Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Masonite is the rapid application Python development framework that strives for beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.
Easily send emails with the Mail Provider and the SMTP and Mailgun drivers
Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers
IOC container and auto resolving dependency injection.
Extremely simple static files configured and ready to go
Create job classes to push to your queues
Active Record style ORM called Orator
An extremely useful command line tool called craft commands
These are all shipped out of the box and ready to go. Use what you need when you need it and forget what you don't.
In order to use Masonite, you’ll need:
Python 3.4+
Pip
If you are running on a Linux flavor, you’ll need a few extra packages. You can download these packages by running:
Or you may need to specify your python version
Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft
command line tool.
We can download our craft
command line tool by just running:
You may have to use sudo if you are on a UNIX machine
Great! We are now ready to create our first project. We should have the new craft
command. We can check this by running:
This should show a list of command options. We are currently only interested in the craft new
command. To create a new project just run:
This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt
file.
You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:
Now lets install our dependencies. We can do this simply by using a craft
command:
Let this install all the required dependencies of Masonite. After it’s done we can just run the server by using another craft
command:
Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications.
There are plenty of ways to contribute to open source. Many of which don't even rely on writing code. A great open source project should have excellent documentation and have as little bugs as possible. Below I will explain how to contribute to this project in different ways.
This is not an exhaustive list and not the only ways to contribute but they are the most common. If you know of other ways to contribute then please let us know.
Of course the project requires contributions to the main development aspects but it's not the only way. But if you would like to contribute to development then a great way to get started is to simply read through this documentation. Get acquainted with how the framework works, how and work and read the documentation starting with the , then the and finally the .
It would also be good to read about the to get familiar with how Masonite does releases (SemVer and RomVer).
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!
The require testing (The main repository does not). If you want to search through all the tests in the tests directories of those repositories and write additional tests and use cases then that will be great! There are already over 100 tests but you can always write more. With more testing comes more stability. Especially as people start to contribute to the project. Check the tests that are already there and write any use cases that are missing. These tests can be things such as special characters in a url or other oddities that may not have been thought of when using TDD for that feature.
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.
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.
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.
Once familiar with the project (by either contributing or by building application using the framework) it would be excellent if you could write or record tutorials and put them on or . In order for the framework to be successful, it needs to have a plethora of documentation even outside of this documentation. It needs to have notoriety and if people are seeing the framework pop up in their favorite locations they will be more inclined to use the framework and contribute to it as well.
This documentation is fantastic but there are spots where it could be improved. Maybe we haven't explained something fully or something just doesn't make sense to you. Masonite uses to host it's documentation and with that you are able to comment directly on the documentation which will start a discussion between you and the documentation collaborators. So if you want to cycle through the documentation page by page and get acquainted with the framework but at the same time contribute to the documentation, this is perfect for you.
If you just don't want to contribute code to the main project you may instead simply report bugs or improvements. You can go ahead and build any of your applications as usual and report any bugs you encounter to the issues page.
Look at the issues page on for any issues, bugs or enhancements that you are willing to fix. If you don't know how to work on them, just comment on the issue and Joseph Mancuso or other core contributors will be more than happy explaining step by step on how you can go about fixing or developing that issue.
Another idea is to use Masonite to build applications such as a screencast website like or an official Masonite website or even a social network around Masonite. Every great framework needs it's "ecosystem" so you may be apart of that buy building these applications with the Masonite branding and logos. Although copying the branding requires an OK from Joseph Mancuso, as long as the website was built with Masonite and looks clean it shouldn't be a problem at all.
Most pull requests will sit inside GitHub for a few days while it gets quality tested. The main develop
branch pull requests could sit there for as much 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.
The Masonite framework itself follows the RomVer versioning schema which is PARADIGM.MAJOR.MINOR although all Masonite packages follow the SemVer versioning schema which is MAJOR.MINOR.FEATURE/BUGFIX.
This means that a framework version of 1.3.20 will have breaking changes with version 1.4.0.
The Masonite main repository (the MiraFramework/masonite
repository) contains only the basic file structure of the application. All of the core framework functionality is inside the MasoniteFramework/core
repository which can be updated as much as every day or once per month and therefore will follow normal SemVer. Because MiraFramework/masonite
does not require major updates, we can follow RomVer nicely and keep the versioning number artificially lower. Any major updates to this repsository will likely just be file structure changes which should rarely happen unless there are major architectural changes.
Masonite is currently on a 1 month major release cycle. This means that once every month will be a new 1.x release. This 1 month release cycle will continue until Masonite has reached a release that is stable enough to move far into the future with that releases architecture.
Once this stable release has been achieved, Masonite will move to a 6 month major release cycle with minor releases as often as every few days to as much as every few months.
v1.3 - February 2018
v1.4 - March 2018
v1.5 - April 2018
v1.6 - May 2018
v2.0 - June 2018
v2.1 - December 2018
Masonite is made up of three different repositories. There is
The main repository where development is done on the repo that installs on developer's systems.
The core repository which is where the main Masonite pip package is located.
The cli repository where the craft command tool is located.
Major 1 month releases will be released on or after the release date when all repositories are able to be released at the same time, as well as passing all tests.
Whenever the MasoniteFramework/cli
and MasoniteFramework/core
repositories are released on Github, Travis CI will run tests and automatically deploy to PyPi. These major version numbers should correspond to the version of Masonite they support. For example, if the MasoniteFramework/masonite
releases to version 1.4, MasoniteFramework/core
should bump up to 1.4.x regardless of changes.
The main repository which is MasoniteFramework/masonite
does not have a corresponding PyPi package and is only for installing new Masonite projects. See the craft new
command under "The Craft Command" documentation.
Once all three repositories are ready for release, they will all be released on GitHub under the respective new version numbers.
Masonite Routing is an extremely simple but powerful routing system that at a minimum takes a url and a controller. Masonite will take this route and match it against the requested route and execute the controller on a match.
All routes are created inside routes/web.py
and are contained in a ROUTES
constant. All routes consist of either a Get()
route or a Post()
route. At the bare minimum, a route will look like:
Most of your routes will consist of a structure like this. All URI’s should have a preceding /
. Routes that should only be executed on Post requests (like a form submission) will look very similar:
Notice the controller here is a string. This is a great way to specify controllers as you do not have to import anything into your web.py
file. All imports will be done in the backend. More on controllers later.
If you wish to not use string controllers and wish to instead import your controller then you can do so by specifying the controller as well as well as only passing a reference to the method. This will look like:
It’s important here to recognize that we initialized the controller but only passed a reference to the method and did not actually call the method. This is so Masonite can pass parameters into the method when it executes the route.
There are a few methods you can use to enhance your routes. Masonite typically uses a setters approach to building instead of a parameter approach so to add functionality, we can simply attach more methods.
We can name our routes so we can utilize these names later when or if we choose to redirect to them. We can specify a route name like so:
It is good convention to name your routes since route url’s can change but the name should always stay the same.
Middleware is a great way to execute classes, tasks or actions either before or after requests. We can specify middleware specific to a route after we have registered it in our config/middleware.py
file but we can go more in detail in the middleware documentation. To add route middleware we can use the middleware method like so:
This middleware will execute either before or after the route is executed depending on the middleware.
All controllers are located in app/http/controllers
but sometimes you may wish to put your controllers in different modules deeper inside the controllers directory. For example, you may wish to put all your product controllers in app/http/controllers/products
or all of your dashboard controllers in app/http/controllers/users
. In order to access these controllers in your routes we can simply specify the controller using our usual dot notation:
Controllers are defaulted to the app/http/controllers
directory but you may wish to completely change the directory for a certain route. We can change this by using the .module()
method:
This will look for the controller in thirdparty.package.users
module instead of the normal app.http.controllers
module.
Very often you’ll need to specify parameters in your route in order to retrieve information from your URI. These parameters could be an id
for the use in retrieving a certain model. Specifying route parameters in Masonite is very easy and simply looks like:
That’s it. This will create a dictionary inside the Request
object which can be found inside our controllers.
In order to retrieve our parameters from the request we can use the param
method on the Request
object like so:
Sometimes you will want to make sure that the route parameter is of a certain type. For example you may want to match a URI like /dashboard/1
but not /dashboard/joseph
. In order to do this we simply need to pass a type to our parameter. If we do not specify a type then our parameter will default to matching all alphanumeric and underscore characters.
This will match all integers but not strings. So for example it will match /dashboard/10283
and not /dashboard/joseph
If we want to match all strings but not integers we can pass:
This will match /dashboard/joseph
and not /dashboard/128372
. Currently only the integer and string types are supported.
You may wish to only render routes if they are on a specific subdomain. For example you may want example.com/dashboard
to route to a different controller than joseph.example.com/dashboard
. To do this we can use the .domain()
method on our routes like so:
This route will match to joseph.example.com/dashboard
but not to example.com/dashboard
or test.example.com/dashboard
.
It may be much more common to match to any subdomain. For this we can pass in an asterisk instead.
This will match all subdomains such as test.example.com/dashboard
, joseph.example.com/dashboard
but not example.com/dashboard
.
If a match is found, it will also add a subdomain
parameter to the Request class. We can retrieve the current subdomain like so:
Controllers are a vital part of Masonite and is mainly what differs it from other Python frameworks that implement the MVC structure differently. Controllers are simply classes with methods. These methods take a self
parameter which is the normal self that Python class methods require. Controller methods can be looked at as function based views if you are coming from django as they are simply methods inside a class and function the exact same way.
Controllers have an added benefit over straight function based views as the developer has access to to a full class they can manipulate however they want but are also not limiting like Django's class based views. They provide a lot of flexibility.
Its very easy to create a controller with Masonite with the help of our craft
command tool. We can simply create a new file inside app/http/controllers
, name the class the same name as the file and then create a class with methods. We can also use the craft controller
command to do all of that for us which is:
When we run this command we now have a new class under app/http/controllers/DashboardController
called DashboardController
. By convention, Masonite expects that all controllers have their own file since it’s an extremely easy way to keep track of all your classes since the class name is the same name as the file but you can obviously name this class wherever you like.
Controller methods are very similar to function based views in a Django application except this is just a normal class method. Our controller methods at a minimum should look like:
All controller methods must have the self parameter. The self
parameter is the normal python self
object which is just an instance of the current class as usual. Nothing special here.
All controller methods are resolved by the container so you may also retrieve additional objects from the container by specifying them as a parameter:
This might look magical to you so be sure the read about the IOC container in the Service Container documentation.
It’s important to note that unlike other frameworks, we do not have to specify our route parameters as parameters in our controller method. We can retrieve the parameters using the Request.param('key')
class method.
Masonite tries to make static files extremely easy and comes with whitenoise out of the box. White noise wraps the WSGI application and listens for certain URI requests that can be resistered in your configuration files.
All configurations that are specific to static files can be found in config/storage.py. In this file you'll find a constant file called STATICFILES
which is simply a dictionary of directories as they keys and aliases as the value.
The directories to include as keys is simply the location of your static file locations. For example, if your css files are in storage/assets/css then put that folder location as the key. For the value, put the alias you want to use in your templates. For this example, we will use css/
as the alias.
For this setup, our STATICFILES constant should look like:
Now in our templates we can use:
Which will get the storage/assets/css/style.css
file.
Thats it! Static files are extremely simple. You are now a master at static files!
Masonite is the rapid application Python development framework that strives for beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.
Easily send emails with the Mail Provider and the SMTP and Mailgun drivers
Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers
IOC container and auto resolving dependency injection.
Extremely simple static files configured and ready to go
Create job classes to push to your queues
Active Record style ORM called Orator
An extremely useful command line tool called craft commands
These are all shipped out of the box and ready to go. Use what you need when you need it and forget what you don't.
In order to use Masonite, you’ll need:
Python 3.4+
Pip
If you are running on a Linux flavor, you’ll need a few extra packages. You can download these packages by running:
Or you may need to specify your python version
Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft
command line tool.
We can download our craft
command line tool by just running:
You may have to use sudo if you are on a UNIX machine
Great! We are now ready to create our first project. We should have the new craft
command. We can check this by running:
This should show a list of command options. We are currently only interested in the craft new
command. To create a new project just run:
This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt
file.
You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:
Now lets install our dependencies. We can do this simply by using a craft
command:
Let this install all the required dependencies of Masonite. After it’s done we can just run the server by using another craft
command:
Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications.
Masonite works on getting rid of all those mundane tasks that developers either dread writing or dread writing over and over again. Because of this, Masonite has several helper functions that allows you to quickly write the code you want to write without worrying about imports or retrieving things from the Service Container. Many things inside the Service Container are simply retrieved using several functions that Masonite sets as builtin functions.
These functions do not require any imports and are simply just available which is similiar to the print()
function. These functions are all set inside the HelpersProvider
Service Provider.
It may make more sense if we take a peak at this Service Provider:
Notice how we simply just add builtin functions via this provider.
The Request class has a simple request()
helper function.
is exactly the same as:
The view()
function is just a shortcut to the View
class.
is exactly the same as:
The auth()
function is a shortcut around getting the current user. We can retrieve the user like so:
is exactly the same as:
This will return None
if there is no user.
We can get the container by using the container()
function
is exactly the same as:
We may need to get some environment variables inside our controller or other parts of our application. For this we can use the env()
function.
is exactly the same as:
We can resolve anything from the container by using his resolve()
function.
is exactly the same as:
That's it! These are simply just functions that are added to Python's builtin functions.
Service Providers are the key building blocks to Masonite. The only thing they do is register things into the Service Container, or retrieve things from the Service Container. You can read more about the Service Container in the documentation. If you look inside the config/application.py
file, you will find a PROVIDERS
list which contains all the Service Providers involved in building the framework.
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.
We can create a Service Provider but simply using a craft command:
This will create a new Service Provider under our app/providers/DashboardProvider.py
. This new Service Provider will have two simple methods, a register
method and a boot
method. We'll explain both in detail.
There are a few architectural examples we will walk through to get you familiar with how Service Providers work under the hood. Let's look at a simple provider and walk through it.
We can see that we have a simple provider that registers the User
model into the container. There are three key features we have to go into detail here.
First, the wsgi = False
just tells Masonite that this specific provider does not need the WSGI server to be running. When the WSGI server first starts, it will execute all service providers that have wsgi
set to False
. Whenever a provider only binds things into the container and we don't need things like requests or routes, then consider setting wsgi
to False
. the ServiceProvider
class we inherited from sets wsgi
to True
by default. Whenever wsgi
is True
then the service provider will fire on every request.
In our register
method, it's important that we only bind things into the container. When the server is booted, Masonite will execute all register methods on all service providers. This is so the boot
method will have access to the entire container.
The boot method will have access to everything that is registered in the container and is actually resolved by the container. Because of this, we can actually rewrite our provider above as this:
This will be exactly the same as above. Notice that the boot
method is resolved by the container.
Great! It's really that simple. Just this knowledge will take you a long way. Take a look at the other service providers to get some inspiration on how you should create yours. Again, if you do create a Service Provider, consider making it available on PyPi so others can install it into their framework.
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.
The Service Container is just a container where classes are loaded into it by key-value pairs, and then can be retrieved by either the key or value. That's it.
The container is contained inside the App
class which is instantiated throughout in the beginning of the framework and passed through various parts of the project such as controllers, middleware and drivers.
We can create easily create service providers using a craft command:
$ craft provider DashboardProvider This will create a new service provider in app/providers/DashboardServiceProvider.py
. Inside our new service provider we will have a register
method and a boot
method. It's important to know what each one does. We'll discuss both in a little bit.
Once you create your service provider, you'll have to add it to your PROVIDERS
list inside your config/application.py
file. This should be a string to the location of the class:
The register
methods on all service providers are executed first, then all the boot
methods are executed after. Because of this, the register
method should only be used to load things into the container.
The boot method is executed on all service providers after the register
methods on all service providers have been called. Because of this, the boot method will have access to everything inside the container and is resolved by Masonite's container.
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 injected into the Request
object and can be retrieved by:
The container can be used in two ways: making and resolving.
Making
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.
Resolving
This is the most useful part of the container. It is possible to retrieve objects from the container by simply passing them into the parameters. Certain aspects of Masonite are resolved such as controller methods, middleware and drivers.
For example, we can hint that we want to get the Request
class and put it into our controller. All controller methods are resolved by the container.
In this example, Masonite will look inside the container for a key called Request
and return that value from the container. Request
is already loaded into the container for you out of the box.
Another way to resolve classes is by using Python 3 annotations:
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.
Pretty powerful stuff, eh?
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.
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.
Publishing packages are a great way for third party packages to integrate into Masonite. They are extremely easy to setup even on existing pip packages. Publishing packages allows any package to create configuration files, routes, controllers, and other integrations that make developing with your packages amazing. You can read about this in the Creating Packages documentation to learn more about how you can integrate your existing packages or future packages with Masonite.
If you are in a virtual environment,craft publish
will not have access to your virtual environment dependencies. In order to fix this, we can add our site packages to ourconfig/packages.py
config file
If you attempt to publish your package without your virtual environment's site_packages file being inside config/packages.py
, you will encounter a ModuleNotFound
error that says it cannot find the integrations file located with the package you are trying to install. This is because Masonite does not have knowledge of your virtual environments dependencies, only your system dependencies.
If you are in a virtual environment then go to yourconfig/packages.py
file and add your virtual environments site_packages folder to theSITE_PACKAGES
list. YourSITE_PACKAGES
list may look something like:
This will allowcraft publish
to find our dependencies installed on our virtual environment. Read the Publishing Packages documentation for more information.
Once done, all future packages that you pip install will be available through the publish command. This configuration should be done as soon as your virtual environment is created so you don't encounter any errors while trying to publish packages.
Creating a command is quite simple with Masonite and allows other developers to use your package in the command line similiar to a craft
command by running craft-vendor
. In this documentation we'll talk about how to create a package that can expand the craft
tool by using craft-vendor
which comes with the masonite-cli
pip package.
It's important to note that craft-vendor
does not have access to your virtual environment by default, only your system packages. What we can do is add our virtual environment's site_packages
directory to our config/packages.py
config file which should look something like:
This will take those paths and temporarily add it to the sys.path
for the duration of the craft-vendor
command.
The craft
command comes with a package
helper and can be used to create the boilerplate for Masonite packages. We can use this boilerplate to quickly create a package that can be used on the masonite command line. Lets create our boiler plate now by navigating to a directory we would like to build our package in and run:
This will create a file structure like:
You can ignore the integration file for now as we won't be using it in this tutorial. This integration file is used to scaffold other Masonite projects (like adding configuration files, controllers, routes etc.)
All Masonite packages that would like users to interact with their package via the command line will need a commands
module. Let's create one now and make our package structure look like:
The craft-vendor
command is separate from our normal craft
command but is primarily for running third party package commands. This is apart of the masonite-cli
package.
How the craft-vendor
command works is there are three possible commands that can be ran after someone installs your package:
This allows for some flexibility in how your commands can be ran. Lets start by explaining how the first command will interact with your package.
When the user runs this command, craft-vendor
will look for a package called testpackage
. Once it is found, it will look into the commands
module and look for a file called testpackage
, and then a function in that file called testpackage
and execute that function. In this instance, we might have a structure like:
and then inside testpackage/commands/testpackage
will look like:
When the user runs this command, it will find the package called testpackage
, look inside the commands
module and for a file called payments.py
and execute a function called payments
. In this instance, we might have a structure like:
and then inside testpackage/commands/payments.py
will look like:
Lastly, When the user runs this command, it will find the package called testpackage
, look inside the commands
module and for a file called payments.py
and execute a function called stripe
. In this instance, we might have a structure like:
and then inside testpackage/commands/payments.py
will look like:
If you have never set up a package before then you'll need to check how to make a .pypirc
file. This file will hold our PyPi credentials.
To upload to PyPi we just have to think of an awesome name for our package and put it in the setup.py
file. Now that you have a super awesome name, we'll just need to run:
which should upload your package with your credentials from your .pypirc
file. Make sure you click the link above and see how to make one.
We can either test our package locally or upload our package to PyPi.
To test our package locally, if you use virtual environments, just go to your Masonite project and activate your virtual environment. Navigate to the folder where you created your package via the command line and run:
If you want to be able to make changes without having to constantly reinstall your package then run
This will install your new package into your virtual environment. Go back to your project root so we can run our craft-vendor
command. If we run craft-vendor testpackage
we should see the respective print message. Try running all three commands and see the different print output. If you are getting an error of ModuleNotFound and you are inside a virtual environment, don't forget to add the site_packages
directory to your config/packages.py
by following the instructions at the top of this documentation.
Middleware is an extremely important aspect of web applications as it allows you to run important code either before or after every request or even before or after certain routes. In this documentation we'll talk about how middleware works, how to consume middleware and how to create your own middleware. Middleware is only ran when the route is found and a status code of 200 will be returned.
Middleware classes are placed inside the app/http/middleware
by convention but can be placed anywhere you like. All middleware are just classes that take in a request and contain a before
method or an after
method.
There are four types of middleware in total:
Middleware ran before every request
Middleware ran after every request
Middleware ran before certain routes
Middleware ran after certain routes
Again, middleware should live inside the app/http/middleware
folder and should look something like:
Middleware constructors are resolved by the container so simply pass in whatever you like in the parameter list and it will be injected for you. Read more about this in the Service Container documentation.
If Masonite is running a “before” middleware, that is middleware that should be ran before the request, Masonite will check all middleware and look for a before
method and execute that. The same for “after” middleware.
You may exclude either class if you do not wish for that middleware to run before or after.
This is a boilerplate for middleware. It's simply a class with a before and/or after method. Creating a middleware is that simple. Let's create a middleware that checks if the user is authenticated and redirect to the login page if they are not. Because we have access to the request object, we can do something like:
That's it! Now we just have to make sure our route picks this up. If we wanted this to execute after a request, we could use the exact same logic in the after
method instead.
Since we are not utilizing the after
method, we may exclude it all together. Masonite will check if the method exists before executing it.
We have one of two configuration constants we need to work with. These constants both reside in our config/middleware.py
file and are HTTP_MIDDLEWARE
and ROUTE_MIDDLEWARE
.
HTTP_MIDDLEWARE
is a simple list which should contain an aggregation of your middleware classes. This constant is a list because all middleware will simply run in succession one after another, similar to Django middleware
Middleware is a string to the module location of your middleware class. If your class is located in app/http/middleware/DashboardMiddleware.py
then the value we place in our middleware configuration will be a string: app.http.middleware.DashboardMiddleware.DashboardMiddleware
. Masonite will locate the class and execute either the before
method or the after
method.
In our config/middleware.py
file this type of middleware may look something like:
ROUTE_MIDDLEWARE
is also simple but instead of a list, it is a dictionary with a custom name as the key and the middleware class as the value. This is so we can specify the middleware based on the key in our routes file.
In our config/middleware.py
file this might look something like:
Using middleware is also simple. If we put our middleware in the HTTP_MIDDLEWARE
constant then we don't have to worry about it anymore. It will run on every successful request, that is when a route match is found from our web.py
file.
If we are using a route middleware, we'll need to specify which route should execute the middleware. To specify which route we can just append a .middleware()
method onto our routes. This will look something like:
This will execute the auth middleware only when the user visits the /dashboard
url and as per our middleware will be redirected to the named route of login
Awesome! You’re now an expert at how middleware works with Masonite.
Very often you will find yourself adding the same variables to a view again and again. This might look something like
This can quickly become annoying and it can be much easier if you can just have a variable available in all your templates. For this, we can "share" a variable with all our templates with the View
class.
The View
class is loaded into our container under the ViewClass
alias. It's important to note that the ViewClass
alias from the container points to the class itself and the View
from the container points to the View.render
method. By looking at the ViewProvider
this will make more sense:
As you can see, we bind the view class itself to ViewClass
and the render method to the View
alias.
We can share variables with all templates by simply specifying them in the .share()
method like so:
The best place to put this is in a new Service Provider. Let's create one now called ViewComposer
.
This will create a new Service Provider under app/providers/ViewComposer.py
and should look like this:
We also don't need it to run on every request so we can set wsgi
to False
. Doing this will only run this provider when the server first boots up. This will minimize the overhead needed on every request:
Great!
Since we need the request, we can throw it in the boot
method which has access to everything registered into the service container, including the Request
class.
Lastly we need to load this into our PROVIDERS
list inside our config/application.py
file.
And we're done! When you next start your server, the request
variable will be available on all templates.
In addition to sharing these variables with all templates, we can also specify only certain templates. All steps will be exactly the same but instead of the .share()
method, we can use the .compose()
method:
Now anytime the dashboard
template is accessed (the one at resources/templates/dashboard.html
) the request
variable will be available.
We can also specify several templates which will do the same as above but this time with the resources/templates/dashboard.html
template AND the resources/templates/dashboard/user.html
template:
Lastly, we can compose a dictionary for all templates:
Note that this has exactly the same behavior as ViewClass.share()
Very often you will need to upload user images such as a profile image. Masonite let's you handle this very elegantly and allows you to upload to both the disk, and Amazon S3 out of the box. The UploadProvider
Service Provider is what adds this functionality. Out of the box Masonite supports the disk
driver which uploads directly to your file system and the s3
driver which uploads directly to your Amazon S3 bucket.
You may build more drivers if you wish to expand Masonite's capabilities. If you do create your driver, consider making it available on PyPi so others may install it into their project.
Read the "Creating an Email Driver" for more information on how to create drivers. Also look at the drivers
directory inside the MasoniteFramework/core
repository.
All uploading configuration settings are inside config/storage.py
. The settings that pertain to file uploading are just the DRIVER
and the DRIVERS
settings.
This setting looks like:
This defaults to the disk
driver. The disk driver will upload directly onto the file system. This driver simply needs one setting which is the location
setting which we can put in the DRIVERS
dictionary:
This will upload all images to the storage/uploads
directory. If you change this directory, make sure the directory exists as Masonite will not create one for you before uploading. Know that the dictionary inside the DRIVERS
dictionary should pertain to the DRIVER
you set. For example, to set the DRIVER
to s3
it will look like this:
Some deployment platforms are Ephemeral. This means that either hourly or daily, they will completely clean their file systems which will lead to the deleting of anything you put on the file system after you deployed it. In other words, any user uploads will be wiped. To get around this, you'll need to upload your images to Amazon S3 or other asset hosting services which is why Masonite comes with Amazon S3 capability out of the box.
Uploading with masonite is extremely simple. We can use the Upload
class which is loaded into the container via the UploadProvider
Service Provider. Whenever a file is uploaded, we can retrieve it using the normal Request.input()
method. This will look something like:
And inside our controller we can do:
That's it! We specified the driver we want to use and just uploaded an image to our file system.
This action will return the file system location. We could use that to input into our database if we want:
We may also need to get the filename of the upload. If the request input is a file upload, we have some additional attributes we can use:
Lastly, we may need to prepend the file name with something like a uuid
or something or even just a normal string. We can do so by using the storePrepend()
method:
Uploading to S3 is exactly the same. Simply add your username, secret key and bucket to the S3 setting:
Make sure that your user has the permission for uploading to your S3 bucket.
Then in our controller:
How the S3 driver currently works is it uploads to your file system using the disk
driver, and then uploads that file to your Amazon S3 bucket. So do not get rid of the disk
setting in the DRIVERS
dictionary.
It's important to know the life cycle of the request so you can understand what is going on under the hood in order to write better software. Whenever you have a better understanding of how your development tools work, you will feel more confident as you'll understand what exactly is going on. This documentation will try to explain in as much detail as needed the flow of the request from initiation to execution. To read about how to use the Request()
class, read the Requests documentation.
The Request()
class is in the Masonite package module and is imported and initialized in bootstrap/start.py
. The Request()
class is initialized at the top of the WSGI server and takes the WSGI environ variable as a dependency. The Request() class will then parse that environ variable into a usable object for Masonite and pass that around down through the frameworks middleware and controllers. If the route to be executed is an API route, Masonite will load the request into the API class so that it can parse the URI and determine which CRUD operation to perform. After all the middleware, controllers and routes have touched the request object, Masonite will determine if the user, at any point, wants to redirect. See the Requests documentation about how redirecting works.
If the request object will be redirecting to a named route, it will cycle through all the routes, locate the route with the name specified, and execute that route. If the request object will be redirecting to a url, it will simply throw a 302 response and send the URI as the Location header on the response. If the user is not being redirected, meaning the redirect method has never been called on the request object, then the response will be a 200 and the data will show as normal.
The request object is initialized at the top of the WSGI server and simply takes the environ variable, which is passed with all WSGI servers and hold information about: the request, server, URI, request method etc. The request object will then parse the environ and store them into certain class attributes. The request object has several methods that assist in extracting these attributes into usable and memorable methods. Read more about how to use the request object in the "Requests" documentation.
The request object is sent into middleware and is subject to change there. Any changes to the request object in middleware classes will change the request object for the rest of there workflow. For example, Masonite comes with a LoadUser
middleware which checks the current user and loads them into the request. This is commented out by default since not all projects need to connect to a database.
If one of the middleware has instructed the request object to redirect, the view that is ready to execute, will not execute.
For example, if the user is planned on going to the dashboard view, but middleware has told the request to redirect to the login page instead, the dashboard view, and therefore the controller, will not execute at all. It will be skipped over. Masonite checks if the request is redirecting before executing a view.
Also, the request object is passed into middleware classes in the constructor so all middleware should load it into the class. Read more about how middleware works in the Middleware documentation.
Masonite comes with a built in API system for testing and personal development. It is not yet ready for production and lacks several authentication features.
The request object that is loaded in the API class will check the base URI and then execute CRUD operations based on the URI. As a developer, you are not responsible for manipulating the request object in your API classes.
Towards the end of the request lifecycle, Masonite will check if Masonite should redirect the user. This happens whenever the redirecting methods are exectuted on the request object. If the user wants to redirect to a named route, Masonite will loop through all the routes inside routes/web.py
and check all the route names. If a name matches, it will execute that route. If the route requires parameters (such that a route has a @variable
in the route URI) then you will need to pass the .send()
method when redirecting. More about request redirecting in the Requests documentation.
If the user is redirecting to a normal URL, it will not check any routes. The request will then send a 302 response and redirect the user to the route specified.
If the request is not redirecting, then it will return a 200 status code response and simply show the output needed. This output will come from the controller.
Finally, the data returned from the controller is then passed into the return statement so the WSGI server can interpret it.
Masonite comes with email support out of the box. Most applications will need to send email upon actions like account creation or notifications. Because email is used so often with software applications, masonite provides mail support with several drivers.
All mail configuration is inside config/mail.py
and contains several well documented options. There are several built in drivers you can use but you can make your own if you'd like. You can follow the documentation here at Creating a Mail Driver. If you do make your own, consider making it available on PyPi so others can install it. We may even put it in Masonite by default.
By default, Masonite uses the smtp
driver. Inside your .env
file, just put your smtp credentials. If you are using Mailgun then switch your driver to mailgun
and put your Mailgun credentials in your .env
file.
There are two drivers out of the box that masonite uses and there is a tiny bit of configuration for both.
The SMTP driver takes several configuration files we can all put in our .env
file.
Because this is SMTP, we can utilize all SMTP services such as mailtrap and gmail.
Thats it! As long as the authentication works, we can send emails. Remember that it is save to put sensitive data in your .env
file because it is not committed to source control and it is inside the .gitignore
file by default.
Mailgun does not use SMTP and instead uses API calls to their service to send emails. Mailgun only requires 2 configuration settings:
as well as changing the DRIVER
inside config/mail.py
Masonite will retrieve the configuration settings for the mailgun driver from the DRIVERS
configuration setting which Masonite has by default, you do not have to change this.
The Mail
class is loaded into the container via the the MailProvider
Service Provider. We can fetch this Mail
class via our controller methods:
We can send an email like so:
All mail drivers are managed by the MailManager
class and bootstrapped with the MailProvider
Service Provider. Let's take a look at that:
We can specify which driver we want to use. Although Masonite will use the DRIVER
variable in our mail
config file by default, we can change the driver on the fly.
You can see in our MailProvider
Service Provider that we can use the MailManager
class to set the driver. We can use this same class to change the driver:
Sending an email may take several seconds so it might be a good idea to create a Job. Jobs are simply Python classes that inherit from the Queueable
class and can be pushed to queues or ran asynchronously. This will look something like:
Instead of taking seconds to send an email, this will seem immediate and be sent using whatever queue driver is set. The async
driver is set by default which requires no additional configuration and simply sends jobs into a new thread to be ran in the background.
Read more about creating Jobs and sending emails asynchronously in the "Queues and Jobs" documentation.
We can specify which driver we want to use. Although Masonite will use the DRIVER
variable in our config file, we can change the driver on the fly.
We can also specify the subject:
You can specify which address you want the email to appear from:
If you don't want to pass a string as the message, you can pass a view template.
This will render the view into a message body and send the email as html. Notice that we didn't pass anything into the send
message
Database migrations in Masonite is very different than other Python frameworks. Other Python frameworks create migrations based on a model which historically uses Data Mapper type ORM's. Because Masonite uses an Active Record ORM by default, Migrations are completely separated from models. This is great as it allows a seamless switch of ORM's without interfering with migrations. In addition to creating this separation of migrations and models, it makes managing the relationship between models and tables extremely basic with very little magic which leads to faster debugging as well as fewer migration issues.
In this documentation, we'll talk about how to make migrations with Masonite.
Because models and migrations are separated, we never have to touch our model in order to make alterations to our database tables. In order to make a migration we can run a craft command:
This command will create a migration for an existing table. A migration on an existing table will migrate into the database in a certain way so it's important to specify the --table
flag in the command.
In order to create a migration file for a new table that doesn't yet exist (but will after the migration) you can instead use the --create
flag like so:
This will create a migration that will create a table, as well as migrate the columns you specify.
Inside the migration file you will see an up()
method and a down()
method. We are only interested in the up()
method. This method specifies what to do when the migration file is migrated. The down()
method is what is executed when the migration is rolled back. Lets walk through creating a blog migration.
We can use in order to build our migration file. First lets run a migration craft command to create a blog table:
This will create a migration file located in databases/migrations
. Lets open this file and add some columns.
After we open it we should see something an up() method that looks like this:
Inside our with statement we can start adding columns.
Lets go ahead and add some columns that can be used for a blog table.
Ok let's go ahead and break down what we just created.
So adding columns is really straight forward and Orator has some great documentation on their website. In order to add a foreign key, we'll need an unsigned integer column which we specified above called:
This will set up our column index to be ready for a foreign key. We can easily specify a foreign key by then typing
What this does is sets a foreign key on the user_id
column which references the id
column on the users
table. That's it! It's that easy and expressive to set up a foreign key.
There are two types of columns that we will need to change over the course of developing our application. Changing columns is extremely simple. If you're using MySQL 5.6.6 and below, see the caveat below.
To change a column, we can just use the .change()
method on it. Since we need to create a new migration to do this, we can do something like:
and then simply create a new migration but use the .change()
method to let Masonite you want to change an existing column instead of adding a new one:
When we run craft migrate
it will change the column instead of adding a new one.
Because of the constraints that foreign keys put on columns prior to MySQL 5.6.6, it's not as straight forward as appending a .change()
to the foreign key column. We must first:
drop the foreign key relationship
change the column
recreate the foreign key
We can do this simply like so:
Very often you will find a need to validate forms after you have submitted them. Masonite comes with a very simple and reusable way to validate input data with the Masonite Validator()
class. In this documentation, we'll talk about how you can create your own validator to use within your project. Masonite uses the validator.py
library for this feature.
The best way to create a reusable validator class within your Masonite project is to create a class and inherit from the Validator
class inside the masonite.validator
module.
We can make a class called RegistrationValidator()
, inherit from masonite.validator.Validator()
and put it inside app/validators/RegistrationValidator.py
like so:
Awesome! By inheriting from Validator
, this will add several key methods to our validator class that we'll need to verify our request input which we'll talk about below.
Since we inherited from Validator
, we have access to a validate()
method which we can call with self.validate()
. Inside this method call, we can use some useful attributes in order to check our request data.
An example of a simple validation will look something like:
By default, Masonite will supply you with a dictionary of errors depending on the validator class. More information on what the default error messages are found below.
Although this is very convenient, you may wish to specify your own error messages. To do so, we can call the self.messages()
method after we validate. This will look like:
This will change the default error messages to the defaults provided. If you do not specify a custom error message for a validation field then the default one will display as usual.
In order to check the input data we receive from a request, such as a form submission, we can use this validator like so:
Notice that we pass in the request inside the constructor of our RequestValidator
class. Our class is using the constructor inherited from Masonite's Validator
class.
Sometimes we may wish to use our validator class without request data. In order to do this we can just pass a dictionary of values into our .check()
method like so:
Notice how we passed a dictionary into our .check()
method here and didn't pass the request object in the constructor.
There are a plethora of options that you can use to validate your forms. In addition to validating your request input, we also get a dictionary of errors. In order to get the errors if a validation fails, we can get use the method:
This method will return a dictionary of errors that will be different depending on the validation class used but this method will return None
if there are no errors. Below each option will be what the value of .errors()
will be as well as how you would use them inside Masonite.
By default, all keys registered for validation are optional. Any key that doesn't exist in the validation will skip any of the missing input data. For example, if a validation is not set for password
then it will simply not check any validation on that specific request input. In this case, we can leave our password
validation out entirely.
Unlike other validator classes, this class does not need to be instantiated (contain parenthesis at the end). So Required
is the correct usage and not Required()
.
Usage
Error
The Truthy()
validator class will check whatever is truthy to Python. This includes True, non-0 integers, non-empty lists, and strings
Usage
Error
This validator will check that a value is equal to the value given.
Usage
Error
Note that all request input data will be a string. so make sure you use Equals('1')
and not Equals(1)
. Just be sure to maintain the data type in your validation.
This validator checks that the dictionary value falls inclusively between the start and end values passed to it.
Error
The Pattern validator checks that the dictionary value matches the regex pattern that was passed to it.
Usage
Error
Since Orator returns a collection, we can specify an Orator Collection as well:
Error
This validator negates a validator that is passed to it and checks the dictionary value against that negated validator.
Usage
Error
This validator checks that the dictionary value is an instance of the base class passed to it, or an instance of one of its subclasses.
Usage
Error
This validator checks that the dictionary value inherits from the base class passed to it. To be clear, this means that the dictionary value is expected to be a class, not an instance of a class.
Usage
Error
This validator checks that value the must have at least minimum elements and optionally at most maximum elements.
Usage
Error
Understanding that modern frameworks need to handle modern web applications. Many developers are starting to use third party packages, like Sass, to write CSS. Normally, many people who write Sass in other frameworks will need to run other third party services like Webpack or grunt. Masonite tries to make this as simple as possible and comes with Sass built in. So you just need to write Sass and it will compile into CSS when you run the server.
Now although Masonite comes with the ability to compile Sass, it is deliberately missing the libsass
dependency. This dependency takes several minutes to install and therefore was left out of the requirements to speed up the process of creating a new project.
Masonite can compile all of your Sass into CSS files when you run the server. The code needed to compile Sass is already inside the framework although it does not execute without libsass
.
In order to activate this feature we can run:
Awesome! We're good to go. All Sass put inside resources/static
will compile into resources/compiled
.
Masonite comes with a configuration file that will allow you to tweak the locations of files that Sass will be looked for, as well as where it will compile into. This setting page can be found in config/storage.py
. The configuration constant looks something like:
This setting will look for base .sass
and .scss
files. Base Sass files are files without a preceding underscore. So style.scss
is a base Sass file but _dashboard.scss
is not. Once all the base Sass files are found, it will compile them into CSS and put them in the location specifed in the compileTo
setting.
This setting is where Masonite will look for files anytime you want to include using the @include
keyword in your sass files. Without the correct location here, Masonite will not find any files you include in your Sass files. This setting can be a list of directory locations.
This setting specifies a single directory you want all of your Sass compiled down into.
This is all setup by default for you and works as soon as you install the libsass
dependency.
Masonite comes with bcrypt out of the box but leaves it up to the developer to actually encrypt things like passwords. You can opt to use any other hashing library but bcrypt is the standard of a lot of libraries and comes with some one way hashing algorithms with no known vulnerabilities. Although, many of hashing algorithms like SHA-1 and MD5 are not secure and you should not use them in your application. You can read the .
Also, we make sure that Javascript cannot read your cookies. It's important to know that although your website may be secure, you are susceptible to attacks if you import third party Javascript packages (since those libraries could be hackable) which can read all cookies on your website and send them to the hacker.
Other frameworks use cryptographic signing which attached a special key to your cookies that prevents manipulation. This does't make sense as a major part of XSS protection is preventing third parties from reading cookies. It doesn't make sense to attach a digital signature to a plaintext cookie if you don't want third parties to see the cookie (such as a session id). Masonite takes one step further and encrypts the entire string and can only be decrypted using your secret key (so make sure you keep it secret).
In your .env
file, you will find a setting called KEY=your-secret-key
. This is the SALT that is used to encrypt and decrypt your cookies. It is important to change this key sometime before development. Masonite does not currently set this key for you. You can generate new secret keys by running:
This will generate a new key in your terminal which you can copy and paste into your .env
file. Your config/application.py
file uses this environment variable to set the KEY
configuration setting.
Remember to not share this secret key as a loss of this key could lead to someone being able to decrypt any cookies set by your application. If you find that your secret key is compromised, just generate a new key.
You can use the same cryptographic signing that Masonite uses to encrypt cookies on any data you want. Just import the masonite.sign.Sign
class. A complete signing will look something like:
By default, Sign()
uses the encryption key in your config/applicaiton.py
file. You could also pass in your own key.
Just remember to store the key you generated or you will not be able to decrypt any values that you encrypted.
Bcrypt is very easy to use an basically consists of a 1 way hash, and then a check to verify if that 1 way hash matches an input given to it. It's important to note that any values passed to bcrypt need to be in bytes.
Again, all values passed into bcrypt need to be in bytes so we can has a password
Notice that the value passed in from the request was converted into bytes using the bytes()
Python function.
Once the password is hashed, we can just safely store it into our database
Do not store unhashed passwords in your database. Also, do not use unsafe encryption methods like MD5 or SHA-1.
In order to check if a password matches it's hashed form, such as trying to login a user, we can use the bcrypt.checkpw()
function:
This will return true if the string 'password'
is equal to the models password.
More information on bcrypt can be found by reading it's documentation.
Because of Masonite's Service Container, It is extremely easy to make drivers that can be used by simply adding your service provider.
Masonite comes shipped with a Service Provider called MailProvider
which loads a few classes into the container as well as boots the default mail driver using the MailManager
. This manager class will fetch drivers from the container and instantiate them. We can look at the MailProvider
class which will gives us a better explanation as to what's going on:
We can see here that because we are only binding things into the container and we don't need the WSGI server to be running, we set wsgi = False
. Service Providers that set wsgi
to False
will only run when the server starts and not on every request.
We can see here that we are binding a few drivers into the container and then binding the MailManager
on boot. Remember that our boot method has access to everything that has been registered into the container. The register methods are executed on all providers before the boot methods are executed.
The MailManager
here is important to understand. When the MailManager
is instantiated, it accepts the container as a parameter. When the MailManager
is instantiated, it fires a create_driver
method which will grab the driver from the configuration file and retrieve a MailXDriver
from the container. The create_driver
method is a very simple method:
Notice that when the driver is created, it tries to get a Mail{0}Driver
from the container. Therefore, all we need to do is register a MailXDriver
into the container ('X' being the name of the driver) and Masonite will know to grab that driver.
So now we know that we need a MailXDriver
so let's walk through how we could create a maildrill
email driver.
We can simply create a class which can become our driver. We do not need to inherit anything, although Masonite comes with a BaseMailDriver
to get you started faster and all drivers should inherit from it for consistency reasons. You can make your driver from a normal class object but it will be harder and won't be considered in Pull Requests.
Let's create a class anywhere we like and inherit from BaseMailDriver
:
Great! We are almost done. We just have to implement one method on this class and that's the send
method. All other methods like to
and template
are inherited from the BaseMailDriver
class. You can find out how to send an email using Maildrill and implement it in this send
method.
We can look at other drivers for inspiration but let's look at the MailMailgunDriver
class now:
If you are wondering where the self.message_body
and self.config
are coming from, check the BaseMailDriver
. All driver constructors are resolved by the service container so you can grab anything you need from the container to make your driver work. Notice here that we don't need a constructor because we inherited it from the BaseMailDriver
Our AppProvider
class might look something like this:
Great! Our new driver is registered into the container. It is now able to be created with Masonite's MailManager
class. We can retrieve your new driver by doing:
If we want the MailManager
to use our new driver by default, change the DRIVER
in our config/mail.py
file. In addition, you may have the users of your driver require a special dictionary entry to the DRIVERS
dictionary:
This way, users can easily swap drivers by simply changing the driver in the config file.
That's it! We just extended our Masonite project and created a new driver. Consider making it available on PyPi so others can install it!
Drivers are simply extensions to features that are managed by the Manager Pattern. If we have a UploadManager
then we might also create a UploadDiskDriver
and a UploadS3Driver
which will be able to upload to both the file system (disk) and Amazon S3. In the future if we have to upload to Microsoft Azure or Google Cloud Storage then we simply create new drivers like UploadAzureDriver
and UploadGoogleStorage
which are very simple to create. Drivers can be as small as a single method or have dozens of methods. The Manager Pattern makes it dead simple to expand the functionality of a Manager and add capabilities to Masonite's features.
Let's go ahead and create a simple driver which is already in the framework called the UploadDiskDriver
.
If you are creating a driver it can live wherever you like but if you are creating it for Masonite core then it should live inside masonite/drivers
. For our UploadDiskDriver
we will create the file: masonite/drivers/UploadDiskDriver.py
.
We should make a class that looks something like:
Simple enough, now we can start coding what our API looks like. In the endgame, we want developers to do something like this from their controllers:
So we can go ahead and make a store
method.
Ok great. Now here is the important part. Our Manager for this driver (which is the UploadManager
) will resolve the constructor of this driver. This basically means that anything we put in our constructor will be automatically injected into this driver. So for our purposes of this driver, we will need the storage and the application configuration.
Now that we have our configuration we need injected into our class, we can go ahead and build out the store()
method.:
Ok great! You can see that our store()
method simply takes the file and write the contents of the fileitem
to the disk.
So now that our driver is created, we can tell our Manager about it. Our manager will know of all drivers that are inside the Service Container. We can create a new service provider which we can use to register classes into our container. Here is an example of what the UploadProvider
will look like:
That's it! Drivers are extremely simple and most drivers you create will be a simple class with a single method or two.
Masonite comes with some authentication out of the box but leaves it up to the developer to implement. Everything is already configured for you by default. The default authentication model is the app/User
model but you can change this in the config/auth.py
configuration file.
There is only a single config/auth.py
configuration file which you can use to set the authentication behavior of your Masonite project. If you wish to change the authentication model, to a app/Company
model for example, feel free to do in this configuration file.
Again the default authentication model is the app/User
model which out of the box comes with a __auth__
class attribute. This attribute should be set to the column that you want to authenticate with. By default your app/User
model will default to the email
column but if you wish to change this to another column such as name
, you can do so here. This will lead your model to look like:
All models that should be authenticated in addition to specifying a __auth__
attribute also needs to have a password
field as well in order to use the out of the box authentication that comes with Masonite.
If you want to authenticate a model, you can use the Auth
facade that ships with Masonite. This is simply a class that is used to authenticate models with a .login()
method.
In order to authenticate a model this will look like:
This will find a model with the supplied username, check if the password matches using bcrypt
and return the model. If it is not found or the password does not match, it will return False
.
Again all authenticating models need to have a password
column. The column being used to authenticate, such as a username or email field can be specified in the model using the __auth__
class attribute.
Changing The Authentication Column
You may change the column to be authenticated by simply changing the column value of the __auth__
class attribute. This will look something like:
This will look inside the username
column and check that column and password. The authentication column is email
by default.
You may of course feel free to roll your own authentication system if you so choose but Masonite comes with one out of the box but left out by default. In order to scaffold this authentication system you can of course use a craft
command:
This will create some controllers, views and routes for you. This command should be used primarily on fresh installs of Masonite but as long as the controllers do not have the same names as the controllers being scaffolded, you will not have any issues.
The views scaffolded will be located under resources/templates/auth
.
After you have ran the craft auth
command, just run the server and navigate to http://localhost:8000/login
and you will now have a login, registration and dashboard. Pretty cool, huh?
Masonite ships with a LoadUser
middleware that will load the user into the request if they are authenticated. Masonite uses the token
cookie in order to retrieve the user using the remember_token
column in the table.
Using this LoadUser
middleware you can retrieve the current user using:
If you wish not to use middleware to load the user into the request you can get the request by again using the Auth
class
If you would like to simply check if the user is authenticated, Request.user()
or Auth(Request).user()
will return False
if the user is not authenticated. This will look like:
Remember that the Request.user()
capability is disabled by default because it currently requires a database connection to work. We can enable this feature by simply uncommented the LoadUserMiddleware
inside the config/middleware.py
file.
Masonite ships with an authentication middleware. You can use this middleware as a route middleware to protect certain routes from non authenticated users. This is great for redirecting users to a login page if they attempt to go to their dashboard.
You can use this middleware in your routes file like so:
By default this will redirect to the route named login
. If you wish to redirect the user to another route or to a different URI, you can edit the middleware in app/http/middleware/AuthenticationMiddleware.py
If you wish to end the session for the user and log them out, you can do so by using the Auth
class. This looks like:
This will delete the cookie that was set when logging in. This will not redirect the user to where they need to go. A complete logout view might look like:
Great! You’ve mastered how Masonite uses authentication. Remember that this is just out of the box functionality and you can create a completely different authentication system but this will suffice for most applications.
Masonite uses an extremely powerful pattern commonly known as the Manager Pattern; also known as the Builder Pattern. Because Masonite uses classes with the XManager
namespace, we will call it the Manager Pattern throughout this documentation.
Think of the Manager Pattern as attaching a Manager to a specific feature. This Manager is responsible for instantiating FeatureXDriver
classes. For example, we attach a UploadManager
to the upload feature. Now the UploadFeature
will instantiate UploadXDriver
classes.
For an actual example inside Masonite, there are currently two classes for the Upload feature: UploadDiskDriver
and UploadS3Driver
. Whenever we set the DRIVER
in our config/storage.py
file to s3
, the UploadManager
will use the UploadS3Driver
to store our files.
This is extremely useful for extending functionality of the managers. If we need to upload to Google, we can just make a UploadGoogleDriver
and put it inside the container. If we set our configuration DRIVER
to google
, our UploadManager
will now use that class to store files.
Masonite obviously comes with several managers such as the UploadManager
and the MailManager
. Let's walk through how to create a new manager called the TaskManager
.
Managers can live wherever they want but if you are developing a manager for the Masonite core package, they will be placed inside masonite/managers
.
Let's create a new file: masonite/managers/TaskManager.py
.
Great! Now all managers should inherit from the masonite.managers.Manager
class. Our TaskManager
should look something like:
Awesome! Inheriting from the Manager class will give our manager almost all the methods it needs. The only thing we need now is to tell this manager how to create drivers. So do to this we need a create_driver()
method.
Perfect. Now the logic of this create driver should be pretty straight forward and should be almost identical to all other managers because we are simply instantiating drivers with different namespaces:
Ok so that was a bit of code. Although it's pretty straight forward, let's explain what's happening here.
So if the driver doesn't exist, we need to create a new driver depending on what's inside the configuration file loaded inside the container. This makes sense because we need to create a driver based off of something. If the developer didn't supply us with one then we need to grab it from the configuration file. Since we are using TaskConfig
here we will eventually have to load it into the container. In a Service Provider, this might look something like:
So we will need to load that into the Service Container in order for this manager to work. We will create a Service Provider later on but for now just know that that's where we get that configuration from. We also capitalize the string to ensure consistency across drivers.
Next we simply call the TaskXDriver
from the container and set that as the driver we want to manage which is done by setting the manage_driver
class attribute.
If we cannot find the driver in the container then we have a KeyError
and we call the DriverNotFound
exception which we imported above. This is only called when the DRIVER
we set in our configuration does not have a corresponding driver inside the container. For example if the developer sets DRIVER='todo'
and we have not set the TaskTodoDriver
Great! We can put this Service Provider in our app/application.py
file inside the PROVIDERS
list. Once that is inside our providers list we can now use this new Task
alias in our controllers like so:
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners or contributors of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
The framework has three main parts.
This official MiraFramework/masonite
repository is where most work will be done, primarily in the develop
or release branches. This is the main repository that will install when creating new projects using the craft new
command.
The `MiraFramework/core
repository where the main masonite
pip package lives.
This is where the from masonite ...
module lives.
The MiraFramework/cli
repository where the craft
command lives
This repo is simple and will be able to be installed following the installation instruction in the README.
Fork the MasoniteFramework/masonite repo.
Clone that repo into your computer:
git clone http://github.com/your-username/masonite.git
Checkout the current release branch (example: develop
)
You should now be on a develop
local branch.
run git pull origin develop
to get the current release version.
From there simply create your feature branches (change-default-orm
) and make your desired changes.
Push to your origin repository:
git push origin change-default-orm
Open a pull request and follow the PR process below
The trick to this is that we need it to be pip installed and then quickly editable until we like it, and then pushed back to the repo for a PR. Do this only if you want to make changes to the core Masonite package
To do this just:
Fork the MasoniteFramework/core
repo,
Clone that repo into your computer:
git clone http://github.com/your-username/core.git
Activate your masonite virtual environment (optional)
Go to where you installed masonite and activate the environment
While inside the virtual environment, cd into the directory you installed core.
Run pip install .
from inside the masonite-core directory. This will install masonite as a pip package.
Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.
craft
commands)Craft commands make up a large part of the workflow for Masonite. Follow these instructions to get the masonite-cli package on your computer and editable.
Fork the MasoniteFramework/cli
repo,
Clone that repo into your computer:
git clone http://github.com/your-username/cli.git
Activate your masonite virtual environment (optional)
Go to where you installed masonite and activate the environment
While inside the virtual environment, cd into the directory you installed cli
Run pip install --editable .
from inside the masonite-cli directory. This will install cli (which contains the craft commands) as a pip package but also keep a reference to the folder so you can make changes freely to craft commands while not having to worry about continuously reinstalling it.
Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.
Comments 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)
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:
Method and Function Docstrings
All methods and functions should also contain a docstring with a brief description of what the module does
For example:
Code Comments
If you're code MUST be complex enough that future developers will not understand it, add a #
comment above it
For normal code this will look something like:
Flagpole Comments
Flag pole comments are a fantastic way to give developers an inside to what is really happening and for now should only be reserved for configuration files. A flag pole comment gets its name from how the comment looks
It's important to note that there should have exactly 75 -
above and below the header and have a trailing |
at the bottom of the comment.
Ensure any changes are well commented and any configuration files that are added have a flagpole comment on the variables it's setting.
Update the README.md and MasoniteFramework/docs
repo with details of changes to the interface, this includes new environment variables, new file locations, container parameters etc.
Must add unit testing for any changes made. Of the three repositories listed above, only the cli
and core
repos require unit testing.
The PR must pass the Travis CI build. The Pull Request can be merged in once you have the sign-off of two other collaborators, or the feature maintainer for your specific feature improvement or the repo owner.
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.
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
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.
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.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at idmann509@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
Notice that we are using a context processor which is our schema builder. All we have to worry about is whats inside it. Notice that we have a table
object that has a few methods that are related to columns. Most of these columns are pretty obvious and you can read about different you can use. We'll mention the foreign key here though.
Check the for more information on creating a migration file.
This feature uses for this kind of encryption. Because of this, we can generate keys using Fernet.
Since the MailManager
class creates the driver on boot, we can simply register the driver into the container via any service providers register method. We could create a new Service Provider and register it there. You can read more about created Service Providers under the documentation. For now, we will just register it from within our AppProvider
.
Great. If you're confused about how the dependency injection Service Container works then read the documentation.
Notice how we set our storage configuration in the container, binded our drivers and then binded our Manager. Again, our manager will be able to find all our UploadXDrivers
that are loaded into the container. So if we set the DRIVER
inside our configuration file to google
, our manager will look for a UploadGoogleDriver
inside our container. Read more about Managers in the documentation.
We can use our manager simply by loading it into the container. We can do this by creating a Service Provider. Learn more about how to create a Service Provider in the documentation. Let's show what a basic Service Provider might look like:
Read about how to create drivers for your Manager class under the documentation.
Increase the version numbers in any example files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is for both core
and cli
or for the main Masontie repo.
This Code of Conduct is adapted from the , version 1.4, available at
Masonite Triggers is a way to add support for triggering classes within various parts of your project. A great use case is to create a class that sends an email and then simple use trigger('sendWelcomeEmail')
anywhere in your project.
At the root of your Masonite project just run:
If publishing does not work, you may be using a virtual environment and may not have the correct site_packages
directory added to your config/packages.py
file. Read more about this in the Publishing Packages documentation.
The publish command will create a new configuration file under config/triggers.py
where you can register all of your trigger classes. To register your class, just enter an alias you’d like to use for your class as the key and then a string with the full module path to the class.
This configuration file may look something like:
Lets make this class in app/triggers/SendWelcomeEmail.py
:
By default, all triggers will fire the action
method on your class.
You may now activate that trigger by using the trigger()
function:
All triggers will default to the action
method. You can specify a different method by calling:
which will call the premium method on the sendWelcomeEmail
class. If your functions needs additional parameters you can specify them as extra parameters such as:
A method with that trigger will look like:
That’s it! Triggers are very simple but very powerful.
Masonite Clerk provides a very expressive and simple syntax to start charging your users with Stripe. In addition to being incredibly easy to setup, Clerk can handle charges, subscriptions, cancellation, subscription swapping, subscription prorating and customer creation. You're going to love it.
First we'll need to install Clerk on our machine. To do this simply run:
Masonite uses the config/payment.py
configuration file. Conveniently, Clerk comes with a publish
command we can use to create this.
If you are in a virtual environment, craft publish
will not have access to your virtual environment dependencies. In order to fix this, we can add our site packages to our config/packages.py
config file
If you are in a virtual environment then go to your config/packages.py
file and add your virtual environments site_packages folder to the SITE_PACKAGES
list. Your SITE_PACKAGES
list may look something like:
This will allow craft publish
to find our dependencies installed on our virtual environment.
If you are in a virtual environment it is important you add your virtual environment’s site_packages
directory to the config/packages.py
file.
We can now run:
This will create a new configuration file in config/payment.py
You'll notice in this new config/payment.py
file we have a config setting that looks like:
Our API keys for key
, and secret
should reside in our .env
file. Just create two entries in your .env
file that looks like:
These API keys can be found in your Stripe dashboard.
We'll assume you want to add billable services to your users so we'll just add a few fields to our users table. Just run:
Inside this migrations up()
method we'll just copy these columns in:
Next we'll do the same thing but will be creating a subscriptions table.
Now just copy and paste these migrations in:
Now that we've finished our migrations lets migrate them into our database:
Lastly we'll add a special class to our users table so we can gain access to a lot of Clerk methods on our model. Let's open our User model and import a new Billable model as well as inherit from it:
That's it! You're all ready to start charging and subscribing users!
NOTE: All references to token are stripe tokens which are sent by the request after a form submission. For testing purposes you can use the 'tok_amex' string which will create a test AMEX card so we don't have to keep submitting forms
To charge a user $1. All amounts are in cents. So below we are charging our user 1000 cents (or $10)
To create a customer
To subcribe a user to a plan
To get the actual subscription model from our payment processor. This is great if you want to make some changes to the subscription manually and then .save()
it after.
To cancel the current subscription
To get the Customer object from our payment processor. This is great if you want to make changes to the customer object manually and then .save()
it
To delete the user as a customer:
To swap the current processor plan for a new plan. This will prorate the current plan by default.
If you do not wish to prorate the user. Read more about prorating from the payment processor you are using.
Check if a user is subscribed to a specific stripe plan
Check to see if a user is subscribed to any one of the plans specified
To see if the user is currently subscribed (to any plan)
To see if a user is subscribed to a specific plan (a local plan, not a stripe plan)
To see if a user is subscribed to any of the plans specified (local plans, not a stripe plans)
Views contain all the HTML that you’re application will use to render to the user. Unlike Django, views in Masonite are your HTML templates. All views are located inside resources/templates
All views are rendered with Jinja2 so can use all the Jinja2 code you are used to. An example view looks like:
Since all views are located in resources/templates
, we can use simply create all of our views manually here or use our craft
command tool. To make a view just run:
This will create a template under resources/templates/hello.html
.
There are several ways we can call views in our controllers. The first recommended way is using the view()
function. Masonite ships with a HelpersProvider
Service Provider. This provider will add several new built in functions to your project. These helper functions can be used as shorthand for several commonly used classes such as the View
and Request
class. See the Helper Functions documentation for more information.
One of the helper functions is the view()
function which is accessible like any other built in Python function.
We can call views in our controllers like so:
This will return the view located at resources/templates/dashboard.html
. We can also specify a deeper folder structure like so:
This will look for the view at resources/templates/profiles/dashboard.html
The View
class is loaded into the container so we can retrieve it in our controller methods like so:
This is exactly the same as using the helper function above. So if you choose to code more explicitly, the option is there for you.
A lot of the time we’ll need to pass in data to our views. This data is passed in with a dictionary that contains a key which is the variable that the view will with the corresponding value. We can pass data to the function like so:
Remember that by passing in parameters like Request
to the controller method, we can retrieve objects from the IOC container. Read more about the IOC container in the Service Container documentation.
This will send a variable named id
to the view which can then be rendered like:
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.
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.
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.
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.
You may now simply upload images to both disk and Amazon S3 storage right out of the box. With the new UploadProvider
service provider you can simply do something like:
As well as support for Amazon S3 by setting the DRIVER
to s3
.
These helper functions are added functions to the builtin Python functions which can be used by simply calling them as usual:
Notice how we never imported anything from the module or Service Container. See the Helper Functions documentation for a more exhaustive list
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:
You can now specify anything that is in the container in your middleware constructor and it will be resolved automatically from the container
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:
Which will target test.example.com/dashboard
and not example.com/dashboard
. Read more about subdomains in the Routing documentation.
By default, masonite will look for routes in the app/http/controllers
namespace but you can change this for individual routes:
This will look for the controller in the thirdparty.routes
module.
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 Queues and Jobs documentation for more information.
The Request class is initialized when the server first starts and changes based on every request by the framework. The Request class is loaded into the IOC container so any Service Provider will have access to it. The IOC container allows all parts of the framework to be resolved by the IOC container and auto inject any dependencies they need. Read more about the IOC container in the Service Container documentation.
The Request
class is bound into the IOC container on every request. This takes the WSGI environment variables generated by your WSGI server as a parameter. This is done already for you by Masonite. This Request
class is initialized inside the AppProvider
Service Provider. We grab this request object by simply passing in Request
into the parameters of anything resolved by the Service Container such as middleware, drivers and controller methods like so:
Masonite ships with a HelpersProvider
Service Provider which adds several helper functions. One of these helper functions is the request()
function. This function will return the request object. Because of this, these two pieces of code are identical:
Notice we didn't import anything at the top of our file and also didn't retrieve any objects from the IOC container. Masonite helper functions act just like any other built in Python function. Read more about helper functions in the Helper Functions documentation.
The Request
has several helper methods attached to it in order to interact with various aspects of the request.
In order to get the current request input variables such as the form data during a POST
request or the query string during a GET
request looks like:
NOTE: There is no difference between GET
and POST
when it comes to getting input data. They are both retrieved through this .input()
method so there is no need to make a distinction if the request is GET
or POST
We can get all the request input variables such as input data from a POST form request or GET data from a query string. This will return all the available request input variables for that request as a dictionary.
To check if some request input data exists:
To get the request parameter retrieved from the url. This is used to get variables inside: /dashboard/@firstname
for example.
You may also set a cookie in the browser. The below code will set a cookie named key
to the value of value
You can get all the cookies set from the browser
You can get a specific cookie set from the browser
Get the current user from the request. This requires the LoadUserMiddleware
middleware which is in Masonite by default but commented out. This middleware should only be used if you have a valid database connection. This will return an instance of the current user.
You can specify a url to redirect to
If the url contains http
than the route will redirect to the external website
You can redirect to a named route
You can also go back to a named route specified from the form input back
. This will get the request input named back
and redirect to that named route. This is great if you want to redirect the user to a login page and then back to where they came from. Just remember during your form submission that you need to supply a back
input.
This is equivalent to:
You can also specify the input parameter that contains the named route
Sometimes your routes may require parameters passed to it such as redirecting to a route that has a url like: /url/@firstname:string/@lastname:string
. For this you can use the send
method. Currently this only works with named routes.
You can load a specific secret key into the request by using:
This will load a secret key into the request which will be used for encryptions purposes throughout your Masonite project. Note that by default, the secret key is pulled from your configuration file so you do NOT need to supply a secret key, but the option is there if you need to change it
Creating packages is very simple for Masonite. You can get a package created and on PyPi is less than 5 minutes. With Masonite packages you'll easily be able to integrate and scaffold all Masonite projects with ease. Masonite comes with several helper functions in order to create packages that can add configuration files, routes, controllers, views and commands.
As a developer, you will be responsible for both making packages and consuming packages. In this documentation we'll talk about both. We'll start by talking about how to make a package and then talk about how to use that package or other third party packages.
Masonite, being a Python framework, can obviously utilize all Python packages that aren’t designed for a specific framework. For example, Masonite can obviously use a library like requests but can’t use Django Rest Framework.
Similarly to how Django Rest Framework was built for Django, you can also build packages specific to Masonite.
There are several key functions that Masonite uses in order to create applications. These include primarily: routes, controllers, views, and craft commands. Creating a package is simple and conveniently Masonite comes with several helper functions in order to create all of these. The developer using your package just needs to run craft publish your-package-name
and your package can scaffold out their application for them.
You do not have to use this functionality and instead have the developer copy and paste things that they need but having great setup process is a great way to promote developer happiness.
Like other parts of Masonite, in order to make a package, we can use a craft command. The craft package
command will scaffold out a simple Masonite package and is fully able to be uploaded directly to PyPi.
This should be done in a separate folder outside of your project.
Let's create our package:
This will create a file structure like:
The integration.py
file is important and should not be removed. This is the file that will be used when our users use the craft publish
command.
If we open this file we'll notice a single boot()
function. Whenever the user (the developer using Masonite) uses the craft publish testpackage
command, craft will execute testpackage.integration.boot()
so it's wise to load anything you want to be executed on in this function.
You'll notice a helper function imported for you at the top. This create_or_append_config()
function does exactly what it says. It will take a config file from your package and put it into the project by either creating it (if it does not exist) or appending it (if it does exist). We'll talk about helper functions later on.
Creating a Config Package
Lets create a simple package that will add or append a config file from our package and into the project.
First lets create a config file inside testpackage/snippets/configs/services.py
. We should now have a project structure like:
Great! inside the services.py
lets put a configuration setting:
Perfect! Now we'll just need to tell PyPi to include this file when we upload it to PyPi. We can do this in our MANIFEST.in
file.
Almost done. Now we just need to put our masonite.package
helper function in our boot file. The location we put in our create_or_append_config()
function should be an absolute path location to our package. To do this, Masonite has put a variable called package_directory
inside our integrations.py
file. Our boot method should look something like:
This will append the configuration file that has the same name as our package configuration file. In this case the configuration file we are creating or appending to is config/services.py
because our packages configuration file is services.py
. If we want to append to another configuration file we can simply change the name of our package configuration file.
Working With Our Package
We can either test our package locally or upload our package to PyPi.
To test our package locally, if you use virtual environments, just go to your Masonite project and activate your virtual environment. Navigate to the folder where you created your package and run:
If you want to be able to make changes without having to constantly reinstall your package then run
This will install your new package into your virtual environment. Go back to your project root so we can run our craft publish
command. If we run craft publish testpackage
we should get a module not found error.
It's important to note that craft publish
does not have access to your virtual environment by default, only your system packages. What we can do is add our virtual environments site_packages
directory to our config/packages.py
config file which should look something like:
This will take that path and add it to the sys.path
for the craft publish
script.
Now if we run craft publish
we should see that our new configuration file is now in config/services.py
file. Awesome! We tried making package support super easy. You of course don't need to integrate directly or scaffold the project but the option is there if you choose to make a package to do so.
Uploading to PyPi
If you have never set up a package before then you'll need to check how to make a .pypirc
file. This file will hold our PyPi credentials.
To upload to PyPi we just have to pick a great name for our package in the setup.py
file. Now that you have a super awesome name, we'll just need to run:
which should upload our package with our credentials in our .pypirc
file. Make sure you click the link above and see how to make once.
If python
doesn’t default to Python 3 or if PyPi throws errors than you may need to run:
Consuming a package.
Now that your package is on PyPi we can just run:
Again, not all packages will need to be published. Only packages that need to scaffold the project. You will know if a package needs to be published by reading the packages install documentation.
These helper functions are used inside the boot
function and are only needed when you need your package to be published.
The location
specified as parameters here are absolute path locations. You can achieve this by using the package_directory
variable in your integration.py
file.
To achieve an absolute path location, this will look like:
All helper functions are located in the masonite.packages
module. To use these functions you’ll need to import the function to be used like:
create_or_append_config(location)
will create a configuration file based on a configuration file from your package.
append_web_routes(location)
will append web routes to the routes/web.py
file. Your web routes should have a +=
to the ROUTES
constant and should look something like:
append_api_routes(location)
will append api routes to a masonite project under routes/api.py
. Your api routes should have a +=
to the ROUTES
constant and should look something like:
create_controller(location)
will take a controller from your package and append it under the app.http.controllers
namespace.
The craft command tool is a powerful developer tool that lets you quickly scaffold your project with models, controllers and views as well as condense nearly everything down to it’s simplest form via the craft namespace.
For example, In Django you may need to do something like:
The craft tool condenses all commonly used commands into its own namespace
All scaffolding of Masonite can be done manually (manually creating a controller and importing the view
function for example) but the craft command tool is used for speeding up development and cutting down on mundane development time.
The possible commands for craft include:
To create an authentication system with a login, register and a dashboard system, just run:
This command will create several new templates, controllers and routes so you don’t need to create an authentication system from scratch, although you can. If you need a custom authentication system, this command will scaffold the project for you so you can go into these new controllers and change them how you see fit.
These new controllers are not apart of the framework itself but now apart of your project. Do not look at editing these controllers as editing the framework source code.
If you wish to scaffold a controller, just run:
This command will create a new controller under app/http/controller
. By convention, all controllers should have an appended “Controller”. For example in order to make a dashboard controller, you should run craft controller DashboardController
and not craft controller Dashboard
although you can name your controllers however you like.
If you’d like to start a new project, you can run:
This will download a zip file of the MasoniteFramework/masonite
repository and unzip it into your current working directory. This command will default to the latest release of the repo.
You may also specify some options. The --version
option will create a new project depending on the releases from the MasoniteFramework/masonite
repository.
Or you can specify the branch you would like to create a new project with:
After you have created a new project, you will have a requirements.txt
file with all of the projects dependencies. In addition to this file, you will also have a .env-example
file which contains a boiler plate of a .env
file. In order to install the dependencies, as well as copy the example environment file to a .env
file, just run:
All frameworks have a way to create migrations in order to manipulate database tables. Masonite uses a little bit of a different approach to migrations than other Python frameworks and makes the developer edit the migration file. This is the command to make a migration for an existing table:
If you are creating a migration for a table that does not exist yet which the migration will create it, you can pass the --create
flag like so:
These two flags will create slightly different types of migrations.
After your migrations have been created, edited, and are ready for migrating, we can now migrate them into the database. To migrate all of your unmigrated migrations, just run:
You can also refresh and rollback all of your migrations and remigrate them. This will basically rebuild your entire database.
You can also rollback all migrations without remigrating
Lastly, you can rollback just the last set of migrations you tried migrating
If you'd like to create a model, you can run:
This will scaffold a model under app/ModelName
and import everything needed.
If you need to create a model in a specific folder starting from the app
folder, then just run:
This will create a model in app/Models/ModelName.py.
Service Providers are a really powerful feature of Masonite. If you'd like to create your own service provider, just run:
This will create a file at app/providers/DashboardProvider.py
Read more about Service Providers under the Service Provider documentation.
Jobs are used for Masonite's queue systems. You can create these Queueable
classes and they will be able to be loaded into different queues. To create a job, run:
This will create a job inside the app/jobs
directory.
Views are simply html files located in resources/templates
and can be created easily from running the command:
This command will create a template at resources/templates/blog.html
You can also create a view deeper inside the resources/templates
directory.
This will create a view under resources/templates/auth/home.html
but keep in mind that it will not create the directory for you. If the auth
directory does not exist, this command will fail.
Jobs are designed to be loaded into queues. We can take time consuming tasks and throw them inside of a Job. We can then use this Job to push to a queue to speed up the performance of our application and prevent bottlenecks and slowdowns.
Jobs will be put inside the app/jobs
directory. See the Queues and Jobs documentation for more information.
You may create a PyPi package with an added integrations.py
file which is specific to Masonite. You can learn more about packages by reading the Creating Packages documentation. To create a package boilerplate, just run:
Packages that are built specifically for Masonite in mind will typically support publishing commands. Publishing commands are a way that packages can scaffold and integrate into Masonite. Publishing commands can allow third parties to: create or append to configuration files, create controllers, create routes and other integrations. Read more about publishing by reading our Publishing Packages documentation. To publish a package just run:
You can run the WSGI server by simply running:
Masonite comes with a way to encrypt data and by default, encrypts all cookies set by the framework. Masonite uses a key
to encrypt and decrypt data. Read the Encryption documentation for more information on encryption.
To generate a secret key
, we can run:
This will generate a 32 bit string which you can paste into your .env
file under the KEY
setting.
Great! You are now a master at the craft command line tool.
Almost all applications can make use of queues. Queues are a great way to make time intensive tasks immediate by sending the task into the background. It's great to send anything and everything into the queue that doesn't require an immediate return value -- such as sending an email or firing an API call. The queue system is loaded into masonite via the QueueProvider
Service Provider.
All configuration settings by default are in the config/queue.py
file. Out of the box, Masonite only supports the async
driver which simply sends jobs into the background using multithreading. You are free to create more drivers. If you do create a driver, consider making it available on PyPi so others can also install it.
Jobs are simply Python classes that inherit the Queueable
class that is provided by Masonite. We can simply create jobs using the craft job
command.
This will create a new job inside app/jobs/SendWelcomeEmail.py
. Our job will look like:
All job constructors are resolved by the container so we can simply pass anything we need as normal:
Whenever jobs are executed, it simply executes the handle method. Because of this we can send our welcome email:
That's it! We just created a job that can send to to the queue!
We can run jobs by using the Queue
alias from the container. Let's run this job from a controller method:
That's it! This job will be loaded into the queue. By default, Masonite uses the async
driver which just sends tasks into the background.
We can also send multiple jobs to the queue by passing more of them into the .push()
method:
Remember that anything that is resolved by the container is able to retrieve anything from the container by simply passing in parameters of objects that are located in the container. Read more about the container in the documentation.
Masonite AuthHub brings a centralized and easy to integrate OAuth system to the Masonite Framework. Simply add a few lines of code and the entire OAuth workflow is done for you.
To install Masonite AuthHub just pip install it:
After authhub
is installed, we just need to publish it.
Masonite AuthHub uses the config/services.py
configuration file. Conveniently, AuthHub comes with a publish
command we can use to create this.
If you are in a virtual environment, craft publish
will not have access to your virtual environment dependencies. In order to fix this, we can add our site packages to our config/packages.py
config file
If you are in a virtual environment then go to your config/packages.py
file and add your virtual environments site_packages folder to the SITE_PACKAGES
list. Your SITE_PACKAGES
list may look something like:
This will allow craft publish
to find our dependencies installed on our virtual environment. Read the Publishing Packages documentation for more information.
Publish AuthHub by running:
This will create or append to the config/services.py
file. If you've published a package that has used the config/services.py
file before than you may have to take the contents of the AUTH_PROVIDERS
dictionary that was created and condense it down into a single dictionary.
After we have published AuthHub we should get a dictionary that looks like:
Just add the corresponding environment variables to your .env
file:
The GITHUB_REDIRECT
url is the url that users will return to after they authenticate. This is likely to match the return URL in your app on the provider you are using. For GitHub, this is called “Authorization callback URL” in your OAuth App’s settings.
AuthHub uses the same syntax for all providers and contains a method of redirecting to the provider as well as a method of getting the response.
To redirect to the provider so you can authorize the user:
Notice the .driver()
method here. This driver will instantiate the driver specified in your configuration setting in the previous step.
If you need to, you can also specify some scopes:
Or pass in a state:
The state will be a value returned back after the user has authenticated. This is good for verifying if the user that sent the request is the one that received it.
To get the user response back after the user has authenticated:
What this method does is:
receives the response back from the provider
gets the code from the query string
exchanges the code for an access token
uses the access token to retrieve and return the user.
Pretty cool, huh?
A complete setup might look something like:
Thats it! Check your platform’s typically response in order to see what is in the user object. It’s a good idea to store the access token in your app/User
table and use that token to perform API requests on behest of the user. Many providers like GitHub, Facebook and Twitter all have great Python libraries you can use the token with.