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...
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...
Loading...
Loading...
There are plenty of ways to contribute to open source. Many of which don't even rely on writing code. A great open source project should have excellent documentation, a thriving community and have as little bugs as possible. Below I will explain how to contribute to this project in different ways both including and exluding code contributions.
This is not an exhaustive list and not the only ways to contribute but they are the most common. If you know of other ways to contribute then please let us know.
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 Controllers and Routing work and read the Architectural Concepts documentation starting with the Request Lifecycle, then the Service Providers and finally the Service Container.
It would also be good to read about the Release Cycle to get familiar with how Masonite does releases (SemVer and RomVer).
Feature Maintainers are people who are in charge of specific features (such as Caching or Creating Packages). These developers will be in charge of reviewing PR's and merging them into the development branch and also have direct contact with the repository owner to discuss.
Feature maintainers must already have significant contributions to development of the repository they are trying to be a Feature Maintainer for. Although they do not have to be contributors to the actual feature they plan to maintain.
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 Masonite pip packages require testing (The main repository does not). If you want to search through all the tests in the tests directories of those repositories and write additional tests and use cases then that will be great! There are already over 100 tests but you can always write more. With more testing comes more stability. Especially as people start to contribute to the project. Check the tests that are already there and write any use cases that are missing. These tests can be things such as special characters in a url or other oddities that may not have been thought of when using TDD for that feature.
Once familiar with the project (by either contributing or by building application using the framework) it would be excellent if you could write or record tutorials and put them on Medium or YouTube. 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.
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.
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 Gitbook.com 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 GitHub.com issues page.
Look at the issues page on GitHub.com 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.
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.
Another idea is to use Masonite to build applications such as a screencast website like LaraCasts.com or an official Masonite website or even a social network around Masonite. Every great framework needs it's "ecosystem" so you may be apart of that by building these applications with the Masonite branding and logos. Although copying the branding requires an OK from Joseph Mancuso, as long as the website was built with Masonite and looks clean it shouldn't be a problem at all.
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.
Most pull requests will sit inside GitHub for a few days while it gets quality tested. The main develop
branch pull requests could sit there for as long as 6 months and will only be merged in on releases. With that being said, you can look at the file changes of these pull requests and ensure they meet the community guidelines, the API is similar to other aspects of the project and that they are being respectful and following pull requests rules in accordance with the Contributing Guide documentation.
Every now and then will be a requires discussion
label on an issue or pull request. If you see this label then be sure to add your thoughts on an issue. All issues are open for discussion and Masonite strives off of developer input so feel free to enter a discussion.
Every framework needs great packages and we as the maintainers of Masonite can only do so much with coming out with great packages and maintaining the framework at the same time. We look forward to our community coming out with awesome additions to the Masonite ecosystem. If you have any issues then be sure to open in the gitter chatroom on the Github homepage.
The Masonite framework itself follows the RomVer versioning schema which is PARADIGM.MAJOR.MINOR
although all Masonite packages follow the SemVer versioning schema which is MAJOR.MINOR.BUGFIX
.
This means that a framework version of 1.3.20 may have breaking changes with version 1.4.0. Masonite uses a 6 month major release cycle in order to maintain it's state as a modern Python web framework. Each release is a solid and stable release and upgrading typically takes minimal time.
The Masonite main repository (the MiraFramework/masonite
repository) contains only the basic file structure of the application. All of the core framework functionality is inside the MasoniteFramework/core
repository which can be updated as much as every day or once per month.
Because MiraFramework/masonite
does not require major updates, we can follow RomVer nicely and keep the versioning number artificially lower. Any major updates to this repsository will likely just be file structure changes which should rarely happen unless there are major architectural changes.
Masonite is currently on a 6 month major release cycle. This means that once six months will be a new 2.x release.
Releases are planned to be upgradable from the previous release in 30 minutes or less. If they break that requirement then they should be considered for the next Paradigm release (the x.0.0
release)
Masonite is made up of three different repositories. There is
The main repository where development is done on the repo that installs on developer's systems.
The core repository which is where the main Masonite pip package is located.
The craft repository where the craft command tool is located.
Major 6 month releases will be released on or after the release date when all repositories are able to be released at the same time, as well as passing all tests.
The core repository is uploaded to PyPi under the new release. At this time, everyone could theoretically install the package. Once core is released, craft is updated for any minor changes it may require. Once these two repositories are completed, the masonite repository is updated and finally released. Once this repo is released to the latest version, all future projects will default to the latest version of Masonite.
After that we will bump the version of Masonite in the documentation and verify and update any documentation that is not up to standard.
Whenever the MasoniteFramework/craft
and MasoniteFramework/core
repositories are released on Github, Travis CI will run tests and automatically deploy to PyPi. These major version numbers should correspond to the version of Masonite they support. For example, if the MasoniteFramework/masonite
releases to version 1.4, MasoniteFramework/core
should bump up to 1.4.x regardless of changes.
This is so developers and maintainers will be able to test the new pre release with their applications. Once all QA tests have passed, it will be marked as a normal release and therefore all new applications created from there on out will be the new release.
Developers will still have the option of doing something like: craft new project_name --version 1.6
and installing that version of Masonite.
Once all three repositories are ready for release, they will all be released on GitHub under the respective new version numbers.
Any developers that are using or have used Masonite
If you can take 5 minutes to complete this survey it would be very much appreciated. Just very simple questions that we can use to make Masonite an even better framework then it already is!
The modern and developer centric Python web framework that strives for an actual batteries included developer tool with a lot of out of the box functionality with an extremely extendable architecture. Masonite is perfect for beginner developers getting into their first web applications as well as experienced devs that need to utilize the full potential of Masonite to get their applications done.
Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Use it for your next SaaS! Try it once and you’ll fall in love.
If you are more of a visual learner you can watch Masonite related tutorial videos at
Easily send emails with the Mail Provider and the SMTP and Mailgun drivers.
Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers.
IOC container and auto resolving dependency injection.
Service Providers to easily add functionality to the framework.
Extremely simple static files configured and ready to go.
Active Record style ORM called Orator.
An extremely useful command line tool called craft commands.
Extremely extendable.
These, among many other features, are all shipped out of the box and ready to go. Use what you need when you need it.
In order to use Masonite, you’ll need:
Python 3.4+
Latest version of OpenSSL
Pip3
All commands of python and pip in this documentation is assuming they are pointing to the corresponding Python 3 versions. If you are having issues with any installation steps just be sure the commands are for Python 3.4+ and not 2.7 or below.
If you are running on a Linux flavor, you’ll need the Python dev package and the libssl package. You can download these packages by running:
Or you may need to specify your python3.x-dev
version:
Masonite excels at being simple to install and get going. We use a simple command line tool that will become your best friend. You’ll never want to develop again without it. We call them craft
commands.
We can download our craft
command line tool by just running:
If you already have craft installed, Masonite 2.2 requires masonite-cli>=2.2.0
so you may have to run with the upgrade flag too.
Great! We are now ready to create our first project. We should have the new craft
command. We can check this by running:
This should show a list of command options. If it doesn't then you may have installed the masonite cli incorrectly. Try uninstalling it and be sure when you install it you install it with a
--user
flag likepip install masonite-cli --user
.
We are currently only interested in the craft new
command. To create a new project just run:
This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt
file.
You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:
or if you are on Windows:
The python
command here is utilizing Python 3. Your machine may run Python 2 (typically 2.7) by default for UNIX machines. You may set an alias on your machine for Python 3 or simply run python3
anytime you see the python
command.
For example, you would run python3 -m venv venv
instead of python -m venv venv
Now lets install our dependencies. We can do this simply by using a craft
command:
This command is just a wrapper around the pip
command. This installs all the required dependencies of Masonite, creates a .env
file for us, generates a new secret key, and puts that secret key in our .env
file. After it’s done we can just run the server by using another craft
command:
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.
The Masonite CLI (also known as craft) will try to find all the commands in your project but may not be able to. In this case you will need to call craft directly using something like:
Masonite has romantic versioning instead of semantic versioning. Because of this, all minor releases (2.0.x) will contain bug fixes and fully backwards compatible feature releases. Be sure to always keep your application up to date with the latest minor release to get the full benefit of Masonite's romantic versioning.
Masonite 1.5 is focused on a few bug fixes and changes to several core classes in order to expand on the support of third party package integration.
Masonite 1.5 is releasing also with a new official package for adding RESTful API's to your project. This package used API Resources which are classes that can add a plethora of capabilities to your API endpoints. You can read more about it at the documentation.
Masonite 1.5 also adds additional support HTTP methods like PUT
, PATCH
and DELETE
. More information about these new routes can be found under the documentation
Nearly all dependencies have been moved to the core Masonite package. The only thing inside the project that is installed using craft new
is the WSGI server (which is waitress by default) and Masonite itself. This will improve the ability to change and update dependencies.
HTML forms only support GET and POST so there is no the ability to add any other HTTP methods like PUT
and PATCH
which will change the actual request method on submission. You can read more about this in the documentation.
In Masonite 1.4 and below was the Api()
route which added some very basic API endpoints. All references to API's have been removed from core and added to the new Masonite Entry official package.
If you would like to use API's you will have to use the Masonite Entry package instead.
You can now add data to sessions using the new Sessions feature which comes with a memory and cookie driver for storing data.
In previous versions, Masonite has not been able to fetch the site packages directory if you were in a virtual environment because virtual environment directories are dynamically named depending on who created it. We have found a way to detect the virtual environment and the site packages directory so now there is no need to add the site packages directory manually to the packages configuration file
The main repository which is MasoniteFramework/masonite
does not have a corresponding PyPi package and is only for installing new Masonite projects. See the craft new
command under documentation. The craft new
command will download a zip of the latest release of Masonite, unzip it and rename the folder. Once the next release for this repository is ready, it will be released but marked as a Pre-release
and therefore will not be installable by the default craft new
command.
Be sure to join the for help or guidance.
If you are having installation issues, be sure to read the documentation.
You can learn more about craft by reading documentation or continue on to learning about how to create web application by first reading the documentation
You can now use functions for routes instead of the classes. These new functions are just wrappers around the classes itself. Read more about this under the documentation.
You can now get and set any header information using the new Request.header
method. This also allows third party packages to manipulate header information. Read more about this in the documentation.
You can now delete cookies using the delete_cookie
method as well as set expiration dates for them. See the documentation for more information.
The Cache driver now has an update method which can update a cache value by key. This is useful if you want to change a key value or increment it. Storing a cache file also now auto creates that directory. Read more about this in the documentation.
Craft commands have been built from the ground up with the cleo package. It's an excellent package that is built around the extendability of commands by using primarily classes (instead of decorator functions). Read more under documentation
It is now possible to add craft commands to craft. You can read more about how under documentation
You can now add more migration directories by adding it to the container with a key ending in MigrationDirectory
. This will add the directory to the list of directory that run when migrate commands are ran. You can read more about this in the documentation.
This page contains all deprecated code that may be removed in future versions of Masonite. These helpers are available to use for the remainder of the 2.1 lifetime but will be removed in either the next version (2.2) or the version after.
Some code inside Masonite becomes obsolete as new features become available. In order to make maintaining the codebase easier and clutter free, Masonite will start emitting deprecation warnings during minor releases of the current release.
For example if you upgrade from 2.1.20 to 2.1.21, you may start seeing deprecation warnings inside your console. You can safely ignore these warnings and your code will still work fine but may not work when upgrading to the next major version (for example from 2.1 to 2.2).
When you see these deprecation warnings, you will see a link as well that comes to this page. You should scroll down this page for examples on how to get your code up to date and not in a deprecated state. You should do this as soon as it becomes convenient for you to do so.
No code is scheduled for deprecation as of yet. This can change with future versions of Masonite
Masonite 1.4 brings several new features to Masonite. These features include caching, template caching, websocket support with Masonite calls Broadcasting and much more testing to make Masonite as stable as possible. If you would like to contribute to Masonite, please read the Contributing Guide and the How To Contribute documentation.
If you are upgrading from Masonite 1.3 then please read the Masonite 1.3 to 1.4 documentation.
We recognize that in order for frameworks to keep up with modern web application, they require real time broadcasting. Masonite 1.4 brings basic broadcasting of events to masonite and comes with two drivers out of the box: pusher
and ably
. If you'd like to create more drivers then you can do so easily by reading the About Drivers documentation. If you do create a driver, please consider making it available on PyPi so others can install it into their projects or open an issue on GitHub and make to add it to the built in drivers.
Masonite now has a built in caching class that you can use to either cache forever or cache for a specific amount of time.
Templates may have a lot of logic that are only updated every few minutes or even every few months. With template caching you can now cache your templates anywhere from every few seconds to every few years. This is an extremely powerful caching technique that will allow your servers to run less intensively and easily increase the performance of your application.
If a page gets hit 100 times every second then you can cache for 5, 10 or 15 seconds at a time to lessen the load on your server.
This feature only activates if you have the CacheProvider
loaded in your PROVIDERS
list. If you try to use these features without that provider then you will be hit with a RequiredContainerBindingNotFound
exception letting you know you are missing a required binding from a service provider. This provider comes out of the box in Masonite 1.4.
We have also updated the code to closely conform to PEP 8 standards.
Because of the caching features, we have added a bootstrap/cache
folder where all caching will be put but you can change this in the new config/cache.py
file.
Masonite 1.4 brings the idea of contracts which are very similar to interfaces in other languages. Contracts ensure that a driver or manager inherits has the same functionality across all classes of the same type.
Cross-Site Request Forgery is a crucial security milestone to hit and Masonite 1.4 brings that ability. With a new Service Provider and middleware, we can now add a simple {{ csrf_field }}
to our forms and ensure we are protected from CSRF attacks.
Managers were very redundant before this release so we made it much easier to create managers with 2 simple class attributes instead of the redundant method. Managers are used to manage features and drivers to Masonite.
Now the constructor of all middleware is resolved by the container. This means you may use the IOC dependency injection techniques like controller methods and drivers.
There were two unused imports in the models that Masonite created. These have been removed completely.
When contributing to this repository, please first discuss the change you wish to make via an issue or in the Masonite Slack channel.
Please note we have a code of conduct, please follow it in all your interactions with the project. You can find it in the base of the core project directory.
The framework has three main parts.
This MasoniteFramework/masonite
repository is the main repository that will install when creating new projects using the craft new
command. This is actually a full Masonite project. Not much development will be done in this repository and won't be changed unless new releases of Masonite require changes in the default installation project.
The MasoniteFramework/core
repository is where the main masonite
pip package lives. This is where the from masonite ...
module lives.
The MasoniteFramework/craft
repository where the craft
command lives. This is the actual command itself and is installed when you run pip install masonite-cli --user
.
You can read about how the framework flows, works and architectural concepts here
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
)
git checkout -b develop
You should now be on a develop
local branch.
Run git pull origin develop
to get the current release version.
From there simply create your feature branches (<feature|fix>-<issue-number>
) and make your desired changes.
Push to your origin repository:
git push origin change-default-orm
Open a pull request and follow the PR process below
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.
This repository has a barebones skeleton of a sample project in order to aid in testing all the features of Masonite against a real project. If you install this as editable by passing the --editable
flag then this may break your project because it will override the modules in this package with your application modules.
craft
commands)Craft commands make up a large part of the workflow for Masonite. Follow these instructions to get the masonite-cli package on your computer and editable.
Fork the MasoniteFramework/craft
repo,
Clone that repo into your computer:
git clone http://github.com/your-username/craft.git
Activate your masonite virtual environment (optional)
Go to where you installed masonite and activate the environment
While inside the virtual environment, cd into the directory you installed cli
Run pip install --editable .
from inside the masonite-cli directory. This will install craft (which contains the craft commands) as a pip package but also keep a reference to the folder so you can make changes freely to craft commands while not having to worry about continuously reinstalling it.
Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.
Comments 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:
All modules should have a docstring at the top of every module file and should look something like:
Notice there are no spaces before and after the sentence.
All methods and functions should also contain a docstring with a brief description of what the module does
For example:
Most methods will require some dependency or parameters. You must specify them like this:
And if your dependency are object it should give the path to the module:
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:
You should open an issue before making any pull requests. Not all features will be added to the framework and some may be better off as a third party package or not be done at all. It wouldn't be good if you worked on a feature for several days and the pull request gets rejected for reasons that could have been discussed in an issue for several minutes.
Ensure any changes are well commented and any configuration files that are added have a docstring comments 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.
Name your branches in the form of feature|fix-<issue-number>
. For example if you are doing a bug fix and the issue number is 576
then name your branch fix-576
. This will help us locate the branches on our systems at later dates. If it is a new feature name it feature-576
.
You must add unit testing for any changes made. Of the three repositories listed above, only the craft
and core
repos require unit testing.
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 RomVer.
The PR must pass the Travis CI build. The Pull Request can be merged in once you have a successful review from two other collaborators, or one review from a maintainer or Masonite creator.
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.
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4
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.
There are no known Masonite specific issues known and if there are then there should be an issue open for them on GitHub. With that being said, some users may experience some difficulties with installing Masonite simply because their computer environment is not the norm, they have never setup Python and may have configured it incorrectly or they have not used Python in a while and have an old version.
Before you get started reading through this FAQ make sure you have:
Python 3.4+
Pip3
Ensure you are installing masonite-cli with pip3
and not pip
.
You are likely running this command on a UNIX based machine like Mac or Linux. In that case you should either run it again with a sudo command or a user command flag:
or
If you ran:
and then run:
and get something like:
then try closing your terminal and reopening it. If that doesn't work then you may be running a pip version connecting to Python 2.7. Try uninstalling it and reinstalling it using pip3:
If that does not work then you may have to run sudo:
You may get a strange error like:
The simple fix may just be to run:
If that doesn't work we can just go back to the lower idna version:
If that does not fix the issue then continue reading.
If the above fix did not work then this likely means you installed masonite-cli using the Python 2.7 pip command. Out of the box, all Mac and Linux based machines have Python 2.7. If you run:
you should get a return value of:
But if you run:
you should get a return value of:
Now pip commands are similar:
Notice here we are using 2 different Python installations.
So if you are getting this error you should uninstall masonite-cli from pip and reinstall it using pip3:
You may have to run sudo to remove and install it and you may need to close your terminal to get it work if you are using a UNIX machine.
If you installed everything successfully and running:
Shows an error that it can't be found then try closing your terminal and opening it again. This should refresh any commands that were recently installed
If you still have errors and on a UNIX based machine try running:
You likely ran:
and hit this weird snag that throws this ambiguous error. You might think this is because of a Python version issue but craft is designed to work on Python 2.7 and 3.4+ (although 2.7 and not thoroughly tested) and you're years of Python experience would make you right but this is special. If you are getting this error then that means you are likely on a UNIX machine, Mac right?
The problem is that your machine does not have sufficient permissions to access these external calls from the command line because your machine does not have permission to do so. You will have to give you machine the command to do so by running:
or whatever your Python 3 version is in the middle. Now try running:
and it should work great!
Masonite 2 brings an incredible new release to the Masonite family. This release brings a lot of new features to Masonite to include new status codes, database seeding, built in cron scheduling, controller constructor resolving, auto-reloading server, a few new internal ways that Masonite handles things, speed improvements to some code elements and so much more. We think developers will be extremely happy with this release.
Upgrading from Masonite 1.6 to Masonite 2.0 shouldn't take very long. On an average sized project, this upgrade should take around 30 minutes. We'll walk you through the changes you have to make to your current project and explain the reasoning behind it.
Checkout the Upgrade Guide for Masonite 1.6 to 2.0
Controller constructors are now resolved by the container so this removed some redundancy within your code and any duplicated auto resolving can now be directly in your constructor:
Read more in the Controllers documentation.
There is a new command that starts a Python shell and imports the container for you already. Test it out to verify that objects are loaded into your container correctly. It's a great debugging tool.
Read more in The Craft Command Introduction documentation.
Masonite 2 ships with an awesome little helper command that allows you to see all the routes in your application
Read more in The Craft Command Introduction documentation.
A huge update to Masonite is the new --reload
flag on the serve command. Now the server will automatically restart when it detects a file change. You can use the -r
flag as a shorthand:
Read more in The Craft Command Introduction documentation.
An incredible new feature is autoloading support. You can now list directories in the new AUTOLOAD
constant in your config/application.py
file and it will automatically load all classes into the container. This is great for loading command and models into the container when the server starts up.
You can also use this class as a standalone class in your own service providers.
Read more in Autoloading documentation.
Updated all libraries to the latest version with the exception of the Pendulum library which latest version is a breaking change and therefore was left out. The breaking change would not be worth it to add the complexity of upgrading so you may upgrade on a per project basis.
Previously you had to import classes like:
Now you can simply specify:
Because of this change we no longer need the same duplicated class names in the PROVIDERS list either.
Read more about changing duplicated class names under the Duplicate Class Names documentation.
Removed the need for the redirection provider completely. You need to remove this from your PROVIDERS
list.
Renamed Request.redirectTo
to Request.redirect_to
Also removed the .send() method and moved the dictionary into a parameter:
Read more in the Requests documentation.
Added a new Request.only method to fetch only specific inputs needed.
Read more in Requests documentation.
Added a new Request.get_request_method()
method to the Request
class.
Read more in Requests documentation.
You can now completely remove fetching of any inputs that Masonite handles internally such as __token and __method when fetching any inputs. This is also great for building third party libraries:
Read more in Requests documentation.
Because of the changes to internal framework variables, there are several changes to the CSRF middleware that comes in every application of Masonite.
Be sure to read the changes in the Upgrade Guide 1.6 to 2.0.
Added a new default package to Masonite that allows scheduling recurring tasks:
Read about Masonite Scheduler under the Task Scheduling documentation.
It's important during development that you have the ability to seed your database with dummy data. This will improve team development with Masonite to get everyones database setup accordingly.
Read more in the Database Seeding documentation.
Now all templates have a new static function in them to improve rendering of static assets
Read more in the Static Files documentation.
You can use the password helper to hash passwords more simply than using straight bcrypt:
Read more in the Encryption documentation.
You can now specify which location in your drivers you want to upload to using a new dot notation:
This will use the directory stored in:
Read more in the Uploading documentation.
Masonite 2 removes the bland error codes such as 404 and 500 errors and replaces them with a cleaner view. This also allows you to add custom error pages.
Read more in the Status Codes documentation.
Providers are now explicitly imported at the top of the file and added to your PROVIDERS list which is now located in config/providers.py
. This completely removes the need for string providers and boosts the performance of the application sustantially
This section of the documentation will contain various tutorials. These are guides that are designed to take you from beginning to end on building various types of projects with Masonite. We may not explain things in much detail for each section as this part of the documentation is designed to just get you familiar with the inner workings of Masonite.
Since this section of the documentation is designed to just get you up and coding with Masonite, any further explanations that should be presented are inside various "hint blocks." Once you are done with the tutorial or simply want to learn more about a topic it is advised that you go through each hint block and follow the links to dive deeper into the reference documentation which does significantly more explaining.
You will see various hint blocks throughout the tutorials. Below are examples of what the various colors represent.
You'll see hint blocks that are green which you should follow if you want to learn more information about the topic currently being discussed.
You'll also see hint blocks that are blue. These should not be ignored and typically contain background information you need to further understand something.
This tutorial will assume you have already installed Masonite. If you haven't, be sure to read the Installation guide to get a fresh install of Masonite up and running. Once you have one up and running or if you already have it running, go ahead and continue on.
In this tutorial we will walk through how to create a blog. We will touch on all the major systems of Masonite and it should give you the confidence to try the more advanced tutorials or build an application yourself.
Typically your first starting point for your Masonite development flow will be to create a route. All routes are located in routes/web.py
and are extremely simple to understand. They consist of a request method and a route method. Routing is simply stating what incoming URI's should direct to which controllers.
For example, to create a GET
request route it will look like:
We'll talk more about the controller in a little bit.
You can read more about routes in the Routing documentation
We will start off by creating a view and controller to create a blog post.
A controller is a simple class that holds controller methods. These controller methods will be what our routes will call so they will contain all of our application's business logic.
Think of a controller method as a function in the views.py
file if you are coming from the Django framework
Let's create our first route now. We can put all routes inside routes/web.py
and inside the ROUTES
list. You'll see we have a route for the home page. Let's add a route for creating blogs.
You'll notice here we have a BlogController@show
string. This means "use the blog controller's show method to render this route". The only problem here is that we don't yet have a blog controller.
Let's create the BlogController
in the next step: Part 2 - Creating Our First Controller
All controllers are located in the app/http/controllers
directory and Masonite promotes 1 controller per file. This has proven efficient for larger application development because most developers use text editors with advanced search features such as Sublime, VSCode or Atom. Switching between classes in this instance is simple and promotes faster development. It's easy to remember where the controller exactly is because the name of the file is the controller.
You can of course move controllers around wherever you like them but the craft command line tool will default to putting them in separate files. If this seems weird to you it might be worth a try to see if you like this opinionated layout.
Like most parts of Masonite, you can scaffold a controller with a craft command:
This will create a controller in app/http/controllers
directory that looks like this:
Simple enough, right? You'll notice we have a show
method we were looking for. These are called "controller methods" and are similiar to what Django calls a "view."
But also notice we now have our show method that we specified in our route earlier.
We can return a lot of different things in our controller but for now we can return a view from our controller. A view in Masonite are html files or "templates". They are not Python objects themselves like other Python frameworks. Views are what the users will see (or view).
This is important as this is our first introduction to Python's IOC container. We specify in our parameter list that we need a view class and Masonite will inject it for us.
For now on we won't focus on the whole controller but just the sections we are worried about. A ...
means there is stuff in between code that we are not worried about:
Notice here we annotated the View
class. This is what Masonite calls "Auto resolving dependency injection". If this doesn't make sense to you right now don't worry. The more you read on the more you will understand.
Be sure to learn more about the Service Container.
You'll notice now that we are returning the blog
view but it does not exist yet.
All views are in the resources/templates
directory. We can create a new file called resources/templates/blog.html
or we can use another craft command:
This will create that template we wanted above for us.
We can put some text in this file like:
and then run the server
and open up http://localhost:8000/blog
. You will see "This is a blog" in your web browser.
Most applications will require some form of authentication. Masonite comes with a craft command to scaffold out an authentication system for you. This should typically be ran on fresh installations of Masonite since it will create controllers routes and views for you.
For our blog, we will need to setup some form of registration so we can get new users to start posting to our blog. We can create an authentication system by running the craft command:
We should get a success message saying that some new assets were created. You can check your controllers folder and you should see a few new controllers there that should handle registrations.
We will check what was created for us in a bit.
In order to register these users, we will need a database. Hopefully you already have some kind of local database setup like MySQL or Postgres but we will assume that you do not. In this case we can just use SQLite.
Now we just need to change a few environment variables so Masonite can create the SQLite database.
These environment variable can be found in the .env
file in the root of the project. Open that file up and you should see a few lines that look like:
Go ahead and change those setting to your connection settings by adding sqlite
to the DB_CONNECTION
variable and whatever you want for your database which will be created for you when you migrate. We will call it blog.db
:
Once you have set the correct credentials, we can go ahead and migrate the database. Out of the box, Masonite has a migration for a users table which will be the foundation of our user. You can edit this user migration before migrating but the default configuration will suit most needs just fine and you can always add or remove columns at a later date.
This will create our users table for us along with a migrations table to keep track of any migrations we add later.
Now that we have the authentication and the migrations all migrated in, let's create our first user. Remember that we ran craft auth
so we have a few new templates and controllers.
Go ahead and run the server:
and head over to http://localhost:8000/register and fill out the form. You can use whatever name and email you like but for this purpose we will use:
Now that we have our authentication setup and we are comfortable with migrating our migrations, let's create a new migration where we will store our posts.
Our posts table should have a few obvious columns that we will simplify for this tutorial part. Let's walk through how we create migrations with Masonite.
Not surprisingly, we have a craft command to create migrations. You can read more about Database Migrations here but we'll simplify it down to the command and explain a little bit of what's going on:
This command simply creates the start of a migration that will create the posts table. By convention, table names should be plural (and model names should be singular but more on this later).
This will create a migration in the databases/migrations
folder. Let's open that up and starting on line 6 we should see something that looks like:
Lets add a title, an author, and a body to our posts tables.
This should be fairly straight forward but if you want to learn more, be sure to read the Database Migrations documentation.
Now we can migrate this migration to create the posts table
Now that we have our tables and migrations all done and we have a posts table, let's create a model for it.
Models in Masonite are a bit different than other Python frameworks. Masonite uses Orator which is an Active Record implementation of an ORM. This bascially means we will not be building our model and then translating that into a migration. Models and migrations are separate in Masonite. Our models will take shape of our tables regardless of what the table looks like.
Again, we can use a craft command to create our model:
Notice we used the singular form for our model. By default, Orator will check for the plural name of the class in our database (in this case posts) by simply appending an "s" onto the model. We will talk about how to specify the table explicitly in a bit.
The model created now resides inside app/Post.py
and when we open it up it should look like:
Simple enough, right? Like previously stated, we don't have to manipulate the model. The model will take shape of the table as we create or change migrations.
Again, the table name that the model is attached to is the plural version of the model (by appending an "s") but if you called your table something different such as "blog" instead of "blogs" we can specify the table name explicitly:
Orator by default protects against mass assignment as a security measure so we will explicitly need to set what columns we would like to be fillable:
The relationship is pretty straight forward here. Remember that we created a foreign key in our migration. We can create that relationship in our model like so:
Because of how Masonite does models, some models may rely on each other so it is typically better to perform the import inside the relationship like we did above to prevent any possibilities of circular imports.
We won't go into much more detail here about different types of relationships but to learn more, read the ORM documentation.
Let's setup a little HTML so we can learn a bit more about how views work. In this part we will setup a really basic template in order to not clog up this part with too much HTML but we will learn the basics enough that you can move forward and create a really awesome blog template (or collect one from the internet).
Now that we have all the models and migrations setup, we have everything in the backend that we need to create a layout and start creating and updating blog posts.
We will also check if the user is logged in before creating a template.
The URL for creating will be located at /blog/create
and will be a simple form for creating a blog post
Notice here we have this strange {{ csrf_field }}
looking text. Masonite comes with CSRF protection so we need a token to render with the CSRF field.
Now because we have a foreign key in our posts table, we need to make sure the user is logged in before creating this so let's change up our template a bit:
auth()
is a view helper function that either returns the current user or returns None
.
Masonite uses Jinja2 templating so if you don't understand this templating, be sure to read their documentation.
For simplicity sake, we won't be styling our blog with something like Bootstrap but it is important to learn how static files such as CSS files work with Masonite so let's walk through how to add a CSS file and add it to our blog.
Firstly, head to storage/static/
and make a blog.css
file and throw anything you like in it. For this tutorial we will make the html page slightly grey.
Now we can add it to our template like so right at the top:
That's it. Static files are really simple. It's important to know how they work but for this tutorial we will ignore them for now and focus on more of the backend.
Javascript files are the same exact thing:
For more information on static files, checkout the Static Files documentaton.
Notice that our action is going to /blog/create
so we need to direct a route to our controller method. In this case we will direct it to a store
method.
Let's open back up routes/web.py and create a new route. Just add this to the ROUTES
list:
and create a new store method on our controller:
Now notice above in the form we are going to be receiving 2 form inputs: title and body. So let's import the Post
model and create a new post with the input.
Notice that we now used request: Request
here. This is the Request
object. Where did this come from? This is the power and beauty of Masonite and your first introduction to the Service Container. The Service Container is an extremely powerful implementation as allows you to ask Masonite for an object (in this case Request
) and get that object. This is an important concept to grasp so be sure to read the documentation further.
Read more about the Service Container here.
Also notice we used an input()
method. Masonite does not discriminate against different request methods so getting input on a GET
or a POST
request doesn't matter. You will always use this input method.
Go ahead and run the server using craft serve and head over to http://localhost:8000/blog
and create a post. This should hit the /blog/create
route with the POST
request method and we should see "post created".
Lets go ahead and show how we can show the posts we just created. In this part we will create 2 new templates to show all posts and a specific post.
Let's create 2 new templates.
Let's start with showing all posts
Let's create a controller for the posts to separate it out from the BlogController
.
Great! So now in our show
method we will show all posts and then we will create a single
method to show a specific post.
Let's get the show
method to return the posts view with all the posts:
We need to add a route for this method:
Our posts view can be very simple:
Go ahead and run the server and head over to http://localhost:8000/posts
route. You should see a basic representation of your posts. If you only see 1, go to http://localhost:8000/blog
to create more so we can show an individual post.
Remember we made our author relationship before. Orator will take that relationship and make an attribute from it so we can display the author's name as well:
Let's repeat the process but change our workflow a bit.
Next we want to just show a single post. We need to add a route for this method:
Notice here we have a @id
string. We can use this to grab that section of the URL in our controller in the next section below.
Let's create a single
method so we show a single post.
We use the param()
method to fetch the id from the URL. Remember this key was set in the route above when we specified the @id
For a real application we might do something like @slug
and then fetch it with request().param('slug')
.
We just need to display 1 post so lets just put together a simple view:
Go ahead and run the server and head over the http://localhost:8000/post/1
route and then http://localhost:8000/post/2
and see how the posts are different.
By now, all of the logic we have gone over so far will take you a long way so let's just finish up quickly with updating and deleting a posts. We'll assume you are comfortable with what we have learned so far so we will run through this faster since this is just more of what were in the previous parts.
Let's just make an update method on the PostController
:
Since we are more comfortable with controllers we can go ahead and make two at once. We made one that shows a view that shows a form to update a post and then one that actually updates the post with the database.
Remember we made 2 controller methods so let's attach them to a route here:
That should be it! We can now update our posts.
Let's expand a bit and made a delete method.
Notice we used a GET
route here, It would be much better to use a POST
method but for simplicity sake will assume you can create one by now. We will just add a link to our update method which will delete the post.
We can throw a delete link right inside our update template:
Great! You now have a blog that you can use to create, view, update and delete posts! Go on to create amazing things!
Masonite 1.6 brings mostly minor changes to the surface layer of Masonite. This release brings a lot of improvements to the engine of Masonite and sets up the framework for the release of 2.0.
Previously, all cookies were set with an HttpOnly flag when setting cookies. This change came after reading several articles about how cookies can be read from Javascript libraries, which is fine, unless those Javascript libraries have been compromised which could lead to a malicious hacker sending your domain name and session cookies to a third party. There is no the ability to turn the HttpOnly flag off when setting cookies by creating cookies like:
Because craft is it's own tool essentially and it needs to work across Masonite versions, all commands have been moved into the Masonite repository itself. Now each version of Masonite maintains it's own commands. The new craft version is 2.0
Before, you had to use the Manager class associated with a driver to switch a driver. For example:
Now you can switch drivers from the driver itself:
This version has been fine tuned for adding packages to Masonite. This version will come along with a new Masonite Billing package. The development of Masonite Billing has discovered some rough spots in package integrations. One of these rough spots were adding controllers that were not in the project. For example, Masonite Billing allows adding a controller that handles incoming Stripe webhooks. Although this was possible before this release, Masonite 1.6 has added a new syntax:
Notice the new forward slash in the beginning where the string controller goes.
Previously, controllers were created as they were specified. For example:
created a DashboardController. Now the "Controller" part of the controller is appended by default for you. Now we can just specify:
to create our DashboardController. You may was to actually just create a controller called Dashboard. We can do this by specifying a flag:
short for "exact"
It's also really good practice to create 1 controller per "action type." For example we might have a BlogController
and a PostController
. It's easy to not be sure what action should be in what controllers or what to name your actions. Now you can create a "Resource Controller" which will give you a list of actions such as show, store
, create
, update
etc etc. If what you want to do does not fit with an action you have then you may want to consider making another controller (such as an AuthorController
)
You can now create these Resource Controllers like:
Just like the global controllers, some packages may require you to add a view that is located in their package (like the new exception debug view in 1.6) so you may now add views in different namespaces:
This will get a template that is located in the masonite package itself.
You can now group routes based on a specific string prefix. This will now look like:
which will compile down into /dashboard/user
and /dashboard/user/1
The container was one of the first features coded in the 1.x release line. For Masonite 1.6 we have revisited how the container resolves objects. Before this release you had to put all annotation classes in the back of the parameter list:
If we put the annotation in the beginning it would have thrown an error because of how the container resolved.
Now we can put them in any order and the container will grab each one and resolve it.
This will now work when previously it did not.
The container will now resolve instances of classes as well. It's a common paradigm to "code to an interface and not an implementation." Because of this paradigm, Masonite comes with contracts that act as interfaces but in addition to this, we can also resolve instances of classes.
For example, all Upload drivers inherit the UploadContract contract so we can simply resolve the UploadContract which will return an Upload Driver:
Notice here that we annotated an UploadContract but got back the actual upload driver.
You can now search the container and "collect" objects from it by key using the new collect method:
which will find all keys in the container such as SentryExceptionHook and SentryWebHook and make a new dictionary out of them.
A complaint a few developers pointed out was that Masonite has too many dependencies. Masonite added Pusher, Ably and Boto3 packages by default which added a bit of overhead, especially if developers have no intentions on real time event broadcasting (which most applications probably won't). These dependencies have now been removed and will throw an exception if they are used without the required dependencies.
Masonite 1.6 + will slowly be rolling out various framework hooks. These hooks will allow developers and third party packages to integrate into various events that are fired throughout the framework. Currently there is the abilitity to tie into the exception hook which will call any objects loaded into the container whenever an exception is hit. This is great if you want to add things like Sentry into your project. Other hooks will be implemented such as View, Mail and Upload Hooks.
Masonite 1.4 brings several new features and a few new files. This is a very simple upgrade and most of the changes were done in the pip package of Masonite. The upgrade from 1.3 to 1.4 should take less than 10 minutes
This requirement file has the masonite>=1.3,<=1.3.99
requirement. This should be changed to masonite>=1.4,<=1.4.99
. You should also run pip install --upgrade -r requirements.txt
to upgrade the Masonite pip package.
There is now a new cache folder under bootstrap/cache
which will be used to store any cached files you use with the caching feature. Simply create a new bootstrap/cache
folder and optionally put a .gitignore
file in it so your source control will pick it up.
Masonite 1.4 brings a new config/cache.py
and config/broadcast.py
files. These files can be found on the GitHub page and can be copied and pasted into your project. Take a look at the new config/cache.py file and the config/broadcast.py file. Just copy and paste those configuration files into your project.
Masonite comes with a lot of out of the box functionality and nearly all of it is optional but Masonite 1.4 ships with three new providers. Most Service Providers are not ran on every request and therefore does not add significant overhead to each request. To add these 3 new Service Providers simple add these to the bottom of the list of framework providers:
Note however that if you add the CsrfProvider
then you will also need the CSRF middleware which is new in Masonite 1.4. Read the section below to add the middleware
Masonite 1.4 adds CSRF protection. So anywhere there is any POST form request, you will need to add the {{ csrf_field }}
to it. For example:
This type of protection prevents cross site forgery. In order to activate this feature, we also need to add the CSRF middleware. Copy and paste the middleware into your project under the app/http/middleware/CsrfMiddleware.py
file.
Lastly, put that middleware into the HTTP_MIDDLEWARE
list inside config/middleware.py
like so:
There has been a slight change in the constants used in the config/database.py file. Mainly just for consistency and coding standards. Your file may have some slight changes but this change is optional. If you do make this change, be sure to change any places in your code where you have used the Orator Query Builder. For example any place you may have:
should now be:
with this change
Not much has changed in the actual project structure of Masonite 1.6 so we just need to make some minor changes to our existing 1.5 project
We just have to change our Masonite dependency version in this file:
Masonite 1.6 now wraps the majority of the application in a try and catch block so we can add exception handling such as the new debug view and other exception features down the road such as Sentry and other error logging.
In the middle of the file (line 45 if you have not made changes to this file) we can simply wrap the application:
This will also display an exception view whenever an exception is it.
That's all the changes we have to make for our project.
Read What's New in Masonite 1.6 for any futher changes you may want or have to make to your code base.
Masonite 2 brings an incredible new release to the Masonite family. This release brings a lot of new features to Masonite to include new status codes, database seeding, built in cron scheduling, controller constructor resolving, auto-reloading server, a few new internal ways that Masonite handles things, speed improvements to some code elements and so much more. We think developers will be extremely happy with this release.
Upgrading from Masonite 1.6 to Masonite 2.0 shouldn't take very long although it does have the largest amount of changes in a single release. On an average sized project, this upgrade should take around 30 minutes. We'll walk you through the changes you have to make to your current project and explain the reasoning behind it
Masonite 2 adds some improvements with imports. Previously we had to import providers and drivers like:
Because of this, all framework Service Providers will need to cut out the redundant last part. The above code should be changed to:
Masonite 2 brings a more explicit way of declaring Service Providers in your application. You'll need to take your current PROVIDERS
list inside the config/application.py
file and move it into a new config/providers.py
file.
Now all Service Providers should be imported at top of the file and added to the list:
String providers will still work but it is not recommended and will not be supported in current and future releases of Masonite.
There are a few changes in the wsgi.py
file and the bootstrap/start.py
file.
In the wsgi.py
file we should add a new import at the top:
Then change the code logic of bootstrapping service providers from:
to:
and change the logic in bootstrap/start.py
to:
Notice here we split the providers list when the server first boots up into two lists which significantly lowers the overhead of each request.
This change should significantly boost speed performances as providers no longer have to be located via pydoc. You should see an immediate decrease in the time it takes for the application to serve a request. Rough time estimates say that this change should increase the request times by about 5x as fast.
Again, with the addition of the above change, any place you have a duplicated class name like:
You can change it to:
Renamed Request.redirectTo to Request.redirect_to. Be sure to change any of these instances accordingly.
All instances of:
should be changed to:
Also removed the .send()
method completely on the Request
class so all instances of:
Need to be changed to:
Some variable internals have changed to prepend a double underscore to them to better symbolize they are being handled internally. Because of this we need to change any instances of csrf_token to __token in the CSRF Middleware file.
You can check for what the class should look like from the MasoniteFramework/masonite repository
Masonite 2 comes with a new autoloader. This can load all classes in any directory you specify right into the Service Container when the server first starts. This is incredibly useful for loading your models, commands or tasks right into the container.
Simply add a new AUTOLOAD
constant in your config/application.py
file. This is the entire section of the autoload configuration.
By default this points to the app directory where models are stored by default but if you moved your models to other directories like app/models or app/admin/models then add those directories to your list:
Be caution that this will autoload all models into the Service Container with the class name as the key and the class as the binding.
Because of a minor rewrite of the Request class, we now do not need the RedirectionProvider. You can remove the RedirectionProvider completely in your PROVIDERS
list.
There is a new status code provider which adds support for adding custom status codes and rendering better default status code pages such as 400 and 500 error pages. This should be added right above the StartResponseProvider:
The .env got a small upgrade and in order to make the APP_DEBUG
variable consistent, it should be set to either True
or False
. Previously this was set to something like true
or false
.
Masonite 2 also removed the APP_LOG_LEVEL
environment variable completely.
That's it! You're all done upgrading Masonite 1.6 to Masonite 2.0. Build something awesome!
Be sure to read about all the changes in Masonite 2 to ensure that your application is completely up to date with many of the latest decisions and be sure to thoroughly test your application. Feel free to open an issue if any problems arise during upgrading.
Masonite 2.1 introduces a few new changes that are designed to correct course for the 2.x family and ensure we can go far into the 2.x family without having to make huge breaking changes. It was questionable whether we should break from the 2.x family and start a new 3.x line. The biggest question was removing (actually disabling) the ability to resolve parameters and go with the more favorable annotation resolving. That could have made Masonite a 3.x line but we have ultimately decided to go with the 2.1 as a course correction. Below you will find all changes that went into making 2.1 awesome. Nearly all of these changes are breaking changes.
It is much easier to contribute to Masonite now since nearly classes have awesome docstrings explaining what they do, their dependencies and what they return.
We have completely removed parameter resolving. We can no longer resolve like this:
in favor of the more explicit:
This is a bit of a big change and will be most of the time spent on refactoring your application to upgrade to Masonite 2.1. If you already used the more explicit version then you won't have to worry about this change. It is still possible to resolve parameters by passing that keyword argument to your container to activate that feature:
This should help with upgrading from 2.0 to 2.1 until you have refactored your application. Then you should deactivate this keyword argument so you can be in line with future 2.x releases.
You can choose to keep it activated if that is how you want to create applications but it won't be officially supported by packages, future releases or in the documentation.
All middleware are now classes:
this is different from the previous string based middleware
Previously when getting an incoming JSON response, we had to get the values via the payload input like so:
which was kind of strange in hindsight. Now we can just straight up use the input:
Again this is only a change for incoming JSON responses. Normal form inputs remain the same.
Previously we had a facades module but it was being unused and we didn't see a future for this module so we moved the only class in this module to it's own class. All instances of:
now become:
We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.
When we say "parsing route parameters" we mean the logic required to parse this:
into a usable form to use on the request class this:
This provider has been completely removed for the more recommended ResponseMiddleware which will need to be added to your HTTP middleware list:
We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.
When we say "parsing route parameters" we mean the logic required to parse this:
into a usable form to use on the request class this:
This provider has been completely removed for the more recommended ResponseMiddleware which will need to be added to your HTTP middleware list:
We also noticed that for some reason we were parsing parameters before we found routes but we only ever needed those parameters inside our routes so we were parsing them whether we found a route or not. We moved the parsing of parameters into the if statement that executes when a route is found.
When we say "parsing route parameters" we mean the logic required to parse this:
into a usable form to use on the request class this:
You can now optionally use .
instead of /
in your views:
We moved the CSRF middleware completely into the core framework and allow developers to extend from it now. This will allow us to fix any security bugs that are apart of the CSRF feature.
You may see this pattern a lot in the future which is only extending classes from the core framework so we can hot fix things much better.
Masonite now has a plethora of docstrings on each and every class by default to really give the developer an understanding about what each default class is actually doing and what it is dependent on.
Masonite is also much more PEP 8 compliant. We removed all instances of triple single quotes: '''
for the more preferred and PEP 8 compliant double quotes """
for docstrings.
We also cleaned a lot of the classes generated by the auth command since those were pretty ugly.
We also removed all instances of helper functions by default since it was confusing developers and was throwing red squiggly marks for text editors. They are still available to be used but they will not be known to developers unless they discover them in the documentation. Now all default code explicitly resolves via the container and helper functions can be used on the developers own terms.
Helper functions are still available but you will need to use them on your own terms.
Now every application has a basic seeding structure setup which is the same as if running the craft seed
command. This is to promote more use of this awesome feature which can be used in migration files for quick seeding of databases for development.
We were previously not able to import code into our migration files or database seeders because the command line tool would not pick up our current working directory to import classes into. Now the migrations module and seeds module have 3 lines of code:
this helpers when running the command line to import code into these modules.
In development you would see a message like:
When you hit a route in development mode. Well you would also hit it in production mode too since that was never turned off. Although this is likely fine, it would slow down the framework significantly under load since it takes a bit of resources to print something that didn't need to be printed. This enables a bit of a performance boost.
This command gets the statuses of all migrations in all directories. To include third party migration directories that are added to your project.
simple
container bindingsSometimes you do not need to bind an object to any key, you just want the object in the container. For this you can now do simple
bindings like this:
This new mail helper can be used globally which points to the default mail driver:
|safe
filters on built in template helpers.We no longer need to do:
We can now simply do:
Previously we had to specify the status code as a string:
in order for these to be used properly. Now we can just specify the status code:
There is quite a bit of things to remember when binding various things into the container. For example when binding commands, the key needs to be postfixed with Command
like ModelCommand
. Now we can do things like:
Along with this there are several other methods to help you bind things into the container without having to remember all the special rules involved, if any.
We now have View Routes on all instances of the normal HTTP classes:
We previously used this method on the Cache class like so:
Now we removed the cache_
prefix and it is just:
We can now use the .without()
method on the request class which returns all inputs except the ones specified:
Previously the port was missing from the database configuration settings. This was fine when using the default connection but did not work unless added to the config.
Instead of doing something like:
We can now use a dictionary:
We can now specify a route with multiple HTTP methods. This can be done like so:
Core can now emit events that can be listened to through the container.
Now you can setup a way to send email verifications into your user signup workflow simply but inherting a class to your User model.
Now all redirections set the status code implicitly instead of explicitly needing to set them.
Now you can use craft middleware MiddlewareName
in order to scaffold middleware like other classes.
All views can optionally use dot notation instead of foward slashes:
is the same as:
We can now do container swapping which is swapping out a class when it is resolved. In other words we may want to change what objects are returned when certain objects are resolved. These objects do not have to be in the container in the first place.
You can now use a env
function to automatically type cast your environment variables turning a numeric into an int:
You can now resolve from a container with a parameter list in addition to custom parameters.
In addition to all the awesome things that craft auth
generates, we now generate password reset views and controllers as well for you
Fixed an issue where custom route compilers was not working well with request parameters
All new 2.1 projects have a seeder setup so you can quickly make some mock users to start off your application. All users have a randomly generated email and the password of "secret".
You can run seeders by running:
When setting headers we had to set the http_prefix to None more times then not. So it is set by default.
This:
can change to:
Originally the code:
would return None
if there was no header. Now this returns a blank string.
There is now an up and down command so you can put that in your application in a maintenance state via craft commands:
There is also a new MaintenanceModeMiddleware
:
We removed the store_prepend()
method on the upload drivers for the filename
keyword arg on the store method.
So this:
now becomes:
Previously you had to append all routes with a /
character. This would look something like:
You can now optionally prefix this without a /
character:
Previously we had to do something like:
Now we can optionally get the parameter from the method definition:
Learn more in the Controllers documentation here.
This is used as a wrapper around I/O operations. It will also be a wrapper around the upload drivers and moving files around and other file management type operations
We can now specify directly in the configuration file whether or not the threading or multiprocessing for the async type operations.
Learn more in the Queues documentation here.
We added 4 new HTTP verbs: HEAD
, CONNECT
, OPTIONS
, TRACE
. You import these and use them like normal:
Learn more in the Routes documentation here.
If the incoming request is a JSON request, Masonite will now return all errors as JSON
This is more of an internal change for Core itself.
Before we had to specify that we wanted the server to auto-reload by specifying a -r flag:
Now we can just specify the serve command it will default to auto-reloading:
You can now specify it to NOT auto-reload by passing in 1 of these 2 commands:
Learn more in the Craft commands documentation here.
By default you can only upload image files because of security reasons but now you can disable that by doing an accept('*') option:
Learn more in the Uploading documentation here.
Learn more in the Views documentation here.
We moved from pytest to unittests for test structures.
Learn more in the Testing documentation here.
Added a new DatabaseTestcase
so we can properly setup and teardown our database. This works for sqlite databases by default to prevent your actual database from being destroyed.
Before in templates we had to specify a path to go back to but most of the time we wanted to go back to the current path.
Instead of:
We can now do:
In order to learn how to use this you can visit the documentation here.
Learn more in the Requests documentation here.
We built a new validation library from scratch and completely ripped out the old validation code. Any current validation code will need to be updated to the new way.
The new way is MUCH better. You can read about it in the new validation section here.
Learn more in the Validation documentation here.
Previously we needed to pass in the request object to the Auth class like this:
Now we have it a bit cleaner and you can just resolve it and the request class will be injected for you
You may not notice anything but now if you bind a class into the container like this:
It will be resolved when you resolve it:
This is why the Auth class no longer needs to accept the request class. Masonite will inject the request class for you when you resolve the class.
This works with all classes and even your custom classes to help manage your application dependencies
Auth
class.You can now do something like:
Learn more in the Authentication documentation here.
Previously, each route's regex was being compiled when Masonite checked for it but we realized this was redundant. So now all route compiling is done before the server starts.
This has given Masonite a bit of a speed boost.
Masonite now has the ability to remember the previous container bindings for each object. This can speed of resolving your code by 10-15x. This is disabled by default as it is still not clear what kind of issues this can cause.
This is scheduled to be set by default in the next major version of Masonite
Learn more in the Service Container documentation here.
with_errors()
method in order to cut down on setting an errors session.Now instead of doing this:
we can now shorten down the flashing of errors and do:
Learn more in the Requests documentation here.
Masonite 2.1 is a fantastic release. It works out a lot of the kinks that were in 2.0 as well as brings several new syntactically good looking code generation
This guide just shows the major changes between version to get your application working on 2.1. You should see the What's New in 2.1 documentation to upgrade smaller parts of your code that are likely to be smaller quality of life improvements.
For 2.1 you will need masonite-cli>=2.1.0
.
Make sure you run:
Middleware has been changed to classes so instead of doing this in your config/middleware.py
file:
You will now import it directly:
This is likely the biggest change in 2.0. Before 2.1 you were able to fetch by key when resolving by doing something like:
We have removed this by default and now you much explicitly import your classes in order to interact with the container resolving:
If you truly do not like this change you can modify your container on a per project basis by adding this to your container constructor in wsgi.py
:
Just know this is not recommended and Masonite may or may not remove this feature entirely at some point in the future.
Previously we were able to do something like this:
Since we never actually created a class from this and you were not able to explicitly resolve this, we utilized the new container swapping in order to swap a class out for this container binding.
All instances above should be changed to:
Don't forgot to also do your boot
methods on your Service Providers as well:
As well as all your middleware and custom code:
You may have classes you binded personally to the container like this:
To get this in line for 2.1 you will need to use Container Swapping in order to be able to resolve this. This is actually an awesome feature.
First go to your provider where you binded it to the container:
and add a container swap right below it by swapping it with a class:
now you can use that class to resolve:
Completely removed the masonite.facades
module and put the only class (the Auth
class) in the masonite.auth
module.
So all instances of:
need to be changed to:
The StartResponseProvider
was not doing anything crazy and it could be achieved with a simple middleware. This speeds up Masonite slightly by offsetting where the response preparing takes place.
Simply remove the StartResponseProvider
from your PROVIDERS
list:
As well as put the new middleware in the HTTP middleware
In 2.0 you had to fetch incoming JSON payloads like this:
So now all instances of the above can be used normally:
CSRF middleware now lives in core and allows you to override some methods or interact with the middleware with class attributes:
Replace your current CSRF Middleware with this new one:
If you made changes to the middleware to prevent middleware from being ran on every request you can now set that as a class attribute:
This also allows any security issues found with CSRF to be handled on all projects quickly instead of everyone having to patch their applications individually.
In migrations (and seeds) you will need to put this import inside a __init__.py
file in order to allow models to be imported into them
There was a slight change in the bootstrap/start.py file
around line 60
.
This line:
Needs to be changed to:
You no longer should bind directly to the Response
key in the container. You should use the new Response object.
All instances of:
should now be:
and any instance of:
should be changed to:
Restructuring some sample code would be changing this:
to this:
The Cache.cache_exists()
has been changed to just Cache.exists()
. You will need to make changes accordingly:
That is all the main changes in 2.1. Go ahead and run your server and you should be good to go. For a more up to date list on small improvements that you can make in your application be sure to checkout the Whats New in 2.1 documentation article.
Although not a critical upgrade, it would be a good idea to replace all instances of retrieval of environment variables with the new masonite.env
function.
Change all instances of this:
with the new env
function:
What this will do is actually type cast accordingly. If you pass a numeric value it will cast it to an int and if you want a boolean if will cast True
, true
, False
, false
to booleans like this:
if you don't want to cast the value you can set the cast
parameter to False
We removed the store_prepend()
method on the upload drivers for the filename
keyword arg on the store method.
So this:
now becomes:
Masonite 1.5 doesn't bring many file changes to Masonite so this upgrade is fairly straight forward and should take less than 10 minutes.
All requirements are now gone with the exception of the WSGI server (waitress
) and the Masonite dependency. You should remove all dependencies and only put:
If you have added your site packages directory to our packages configuration file, you can now remove this because Craft commands can now detect your site packages directory in your virtual environment.
Remove the masonite.providers.ApiProvider.ApiProvider
from the PROVIDERS
list as this has been removed completely in 1.5
If you are using the Api()
route inside routes/api.py
for API endpoints then remove this as well. You will need to implement API endpoints using the new Official Masonite Entry package instead.
You'll also have to add a new RESOURCES = []
line to your routes/api.py
file for the new Masonite Entry package if you choose to use it.
This release works with the new craft command release. Upgrade to version masonite-cli / 1.1+
. <1.1
will only work with Masonite 1.4 and below.
Simply run:
You may have to run sudo if you are using a UNIX machine.
Masonite 1.5 now has sessions that can be used to hold temporary data. It comes with the cookie and memory drivers. Memory stores all data in a class which is lost when the server restarts and the cookie driver sets cookies in the browser.
There is a new config/session.py
file you can copy and paste:
As well as add the SessionProvider
inside your PROVIDERS
list just below the AppProvider
:
That's it! You have officially upgrades to Masonite 1.5
Welcome to the upgrade guide to get your Masonite 2.1 application working with Masonite 2.2. We'll be focusing on all the breaking changes so we can get all your code working on a Masonite 2.2 release cycle.
We will not go into all the better ways to use some of the features. For those changes be sure to read the "" documentation to the left to see what fits into your application and what doesn't. We will only focus on the breaking changes here.
Masonite 2.2 is jam packed with amazing new features and most of which are backwards compatible so upgrading from Masonite 2.1 to 2.2 is really simple.
We'll go through each section that your application will need to be upgraded and how it can be done.
Each upgrade will have an impact rating from LOW to HIGH. The lower the rating, the less likely it will be that your specific application needs the upgrade.
First let's upgrade Masonite to 2.2 first so we can see any exceptions that will be raised.
Let's upgrade by doing:
You can also add it to your requirements.txt or Pipfile.
In Masonite 2.1, route helpers were deprecated and you likely started receiving deprecation warnings. In Masonite 2.2, these were removed. You may have had routes that looks like this:
You will now need to remove all these and use the class based ones. To make this easier we can just import the get and post helpers and alias them like this:
Impact: MEDIUM
Masonite 2.2 completely removes the validation library that shipped with Masonite in favor of a brand new one that was built specifically for Masonite.
You'll need to add a new validation provider if you want your application to have the new validation features.
Add it by importing it into config/providers.py
and add it to your PROVIDERS
list:
Masonite 2.2 completely removed the validation package from 2.1 and created an even better all new validation package. You'll have to remove all your validation classes and use the new validation package.
For example you may have had a validation class that looked like this:
and used it inside your views like this:
This is now completely changed to use a better and more sleeker validation. The above validation can now be written like this:
Masonite 2.2 changes a bit how the masonite.auth.Auth
class resolves out of the container and how it resolves its own dependencies.
Now instead of doing something like:
You'll need to move this into the parameter list so it can be resolved:
There should be quite a bit of these in your application if you have used this class or you have used the built in craft auth
scaffold command.
Impact: MEDIUM
The behavior for resolving classes has now been changed. If you bind a class into the container like this:
It previously would have resolved and gave back the class:
This will now resolve from the container when you resolve it as a parameter list. This means that you will never get back a class inside places like controllers.
Now the above code would look something like this:
notice it now returns an object. This is because Masonite will check before it resolves the class if the class itself needs to be resolved (if it is a class). If SomeClass
requires the request object, it will be passed automatically when you resolve it.
Masonite 2.2 focused a lot on new testing aspects of Masonite and has some big rewrites of the package internally.
The UnitTest class has been completely removed in favor of the new masonite.testing.TestCase
method.
An import that looked like this:
Should now look like this:
All classes have now been changed to unittest classes. This will still work with pytest and you can still run python -m pytest
. The only thing that changes is the structure of the setup_method()
. This has been renamed to setUp()
.
A class like this:
Should now look like this:
Previously all methods were snake_case
but to continue with the unittest convention, all testing methods are camelCase
.
A method like:
Now becomes:
Again this is to prevent developers from needing to switch between snake_case
and camelCase
when using Masonite methods and unittest methods.
The route method that looked something like this:
Has now been replaced with the method name of the route. So to get a GET route you would do:
or a POST route:
So be sure to update all methods of self.route()
with the correct request methods.
In 2.1 you had to manually load your routes in like this:
this is no longer required and routes will be found automatically. There is no longer a self.routes()
method.
The JSON method signature has changed and you now should specify the request method as the first parameter.
A previous call that looked like this:
should become:
Previously you logged a user in by using the user
method but now you can using the actingAs
method before you call the route:
A method like this:
Should now be:
Built in global helper functions were removed by default in v2.1 though they are not deprecated and you can use them as you wish.
Masonite works on getting rid of all those mundane tasks that developers either dread writing or dread writing over and over again. Because of this, Masonite has several helper functions that allows you to quickly write the code you want to write without worrying about imports or retrieving things from the Service Container. Many things inside the Service Container are simply retrieved using several functions that Masonite sets as builtin functions which we call "Built in Helper Functions" which you may see them referred to as.
These functions do not require any imports and are simply just available which is similiar to the print()
function. These functions are all set inside the HelpersProvider
Service Provider.
You can continue to use these helper functions as much as you like but most developers use these to quickly mock things up and then come back to refactor later.
It may make more sense if we take a peak at this Service Provider:
Notice how we simply just add builtin functions via this provider.
The below list of helpers are "builtin" helpers meaning they are global in the same way that the print
method is global. These helpers can be used without any imports.
The Request class has a simple request()
helper function.
is exactly the same as:
Notice we didn't import anything at the top of the file, nor did we inject anything from the Service Container.
The view()
function is just a shortcut to the View
class.
is exactly the same as:
Instead of resolving the mail class you can use the mail helper:
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 so in a real world application this may look something like:
This is because you can't call the .id
attribute on None
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 this resolve()
function.
is exactly the same as:
That's it! These are simply just functions that are added to Python's builtin functions.
Die and dump is a common way to debug objects in PHP and other programming languages. Laravel has the concept of dd() which dies and dumps the object you need to inspect.
dd()
is essentially adding a break point in your code which dumps the properties of an object to your browser.
For example we can die and dump the user we find:
If we then go to the browser and visit this URL as normal then we can now see the object fully inspected which will kill the script wherever it is in place and throw an exception but instead of showing the normal debugger it will use a custom exception handler and show the inspection of the object instead:
There are several helper methods that require you to import them in order to use them. These helpers are not global like the previous helpers.
The config helper is used to get values in the config directory. For example in order to get the location in the config/storage.py
file for example.
This function can be used to retrieve values from any configuration file but we will use the config/storage.py
file as an example.
With a config/storage.py
file like this:
We can get the value of the west key in the location inner dictionary like so:
Instead of importing the dictionary itself:
Note the use of the lowercase storage.drivers.s3
instead of storage.DRIVERS.s3
. Either or would work because the config function is uppercase and lowercase insensitive.
This helper that allows you to wrap any object in this helper and call attributes or methods on it even if they don't exist. If they exist then it will return the method, if it doesn't exist it will return None
.
Take this example where we would normally write:
We can now use this code snippet instead:
Compact is a really nice helper that allows you to stop making those really repetitive dictionary statements in your controller methods
take this for example:
Notice how our Python variables are exactly the same as what we want our variables to be in our template.
With the compact function, now you can do:
You can also pass in a dictionary which will update accordingly:
You can use the same Collection class that orator uses when returning model collections. This can be used like so:
You have access to all the methods on a normal collection object.
Controllers are a vital part of Masonite and is mainly what differs it from other Python frameworks which all implement the MVC structure differently. Controllers are simply classes with methods. These methods take a self
parameter which is the normal self that Python methods require. Controller methods can be looked at as "function based views" if you are coming from Django as they are simply methods inside a class and work in similar ways.
Controllers have an added benefit over straight function based views as the developer has access to a full class they can manipulate however they want. In other words, controller methods may utilize class attributes, private methods and class constructors to break up and abstract logic. They provide a lot of flexibility.
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 in app/http/controllers/DashboardController.py
called DashboardController
. By convention, Masonite expects that all controllers have their own file since it’s an extremely easy way to keep track of all your classes since the class name is the same name as the file. This is very opionated but you can obviously put this class wherever you like.
Notice that we passed in Dashboard
but created a DashboardController
. Masonite will always assume you want to append Controller
to the end.
Remember that Masonite will automatically append Controller to the end of all controllers. If you want to create the exact name of the controller then you can pass a -e
or --exact
flag.
or
This will create a Dashboard
controller located in app/http/controllers/Dashboard.py
Resource controllers are controllers that have basic CRUD / resource style methods to them such as create, update, show, store etc. We can create a resource controller by running:
or
this will create a controller that looks like:
Controller methods are very similar to function based views in a Django application. Our controller methods at a minimum should look like:
If you are new to Python, all controller methods must have the self parameter. The self
parameter is the normal python self
object which is just an instance of the current class as usual. Nothing special here.
All controller methods and constructors are resolved by the container so you may also retrieve additional objects from the container by specifying them as a parameter in the method:
or by specifying them in the constructor:
If you need a class in multiple controller methods then it is recommended to put it into the constructor in order to keep the controller DRY.
It’s important to note that unlike other frameworks, we do not have to specify our route parameters as parameters in our controller method. We can retrieve the parameters using the request.param('key')
class method.
You can return JSON in a few different ways. The first way is returning a dictionary which will then be parsed to JSON:
you may return a list:
Or you may even return a model instance or collection. Take these 2 code snippets as an example:
or
Returning a paginated response directly will include the collection data, along with results metadata.
Query parameters accepted are: 'page_size' and 'page'. These will be handled directly as part of the response, there is no need to pass them in explicitly.
Orator classes LengthAwarePaginator and Paginator will return slightly different responses.
LengthAwarePaginator:
returns:
Paginator:
returns:
Optionally you can pass route parameters along with your resolving code. This is useful to keep a nice clean codebase.
For example, these two code snippets are the same:
And this:
You can specify parameters along with any other container resolving.
Learn more by reading the documentation.
The Request class is initialized when the server first starts and is modified on every request. This means that the Request class acts as a singleton and is not reinitialized on every request. This presents both pros and cons during developing Masonite. It's great to not have to worry about a new object being instantiated every time but the con is that some attributes need to be reset at the end of the request.
The Request class is loaded into the IOC container first so any Service Provider will have access to it. The IOC container allows all parts of the framework to be resolved by the IOC container and auto inject any dependencies they need.
Read more about the IOC container in the documentation.
The Request class is bound into the IOC container once when the server is first started. This takes the WSGI environment variables generated by your WSGI server as a parameter. Because of this, we reload the WSGI values on every request but the actual Request object does not change. In other words, the memory address of the Request object is always the same but the class attributes will change of every request. This is done already for you by the Masonite framework itself. This Request class is bound and initialized inside the AppProvider
Service Provider. We grab this request object by simply passing in Request
into the parameters of anything resolved by the Service Container such as middleware, drivers and controller methods like so:
Masonite is smart enough to know that we need the Request
class and it will inject it into our method for us.
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.
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:
There is no difference between any HTTP methods (GET, POST, PUT, etc) when it comes to getting input data. They are all retrieved through this .input()
method so there is no need to make a distinction if the request is GET
or POST
We can get all the request input variables such as input data from a form request or GET data from a query string. Note that it does not matter what HTTP method you are using, the input method will know what input data to get dependent on the current HTTP method (GET
, POST
, PUT
, etc)
This will return all the available request input variables for that request as a dictionary.
This method will get all of the request input variables to include any internal framework variables completely handled internally such as __token and __method. You can exclude them by passing in False into the method or specifying it explicitly:
To get a specific input:
Input data will be cleaned of HTML tags and other security measures. This may cause unwanted return values if you are expecting something like a JSON string. If you want to opt to not clean the input you can specify that as a keyword argument:
To check if some request input data exists:
If your input is a dictionary you have two choices how you want to access the dictionary. You can either access it normally:
Or you can use dot notation to fetch the value for simplicity:
You can also use a * wildcard to get all values from a dictionary list. Take this code example:
You can only get a certain set of parameters if you have a need to do so. This can be used like:
We can specify a set of parameters to exclude from the inputs returned. For example:
Notice it returned everything besides lastname
.
To get the request parameter retrieved from the url. This is used to get variables inside: /dashboard/@firstname
for example.
Sometimes you may want to handle incoming JSON requests. This could be form external API's like Github.
Masonite will detect that an incoming request is a JSON request and put the cast the JSON to a dictionary and load it into the payload request input. For example if you have an incoming request of:
Then we can fetch this input in a controller using the normal input()
method like so:
You may also set a cookie in the browser. The below code will set a cookie named key
to the value of value
.
By default, all cookies are encrypted with your secret key which is generated in your .env
file when you installed Masonite. This is a security measure to ensure malicious Javascript code cannot fetch cookies if they are somehow retrieved. All cookies are set with the HTTP_ONLY flag meaning that Javascript cannot read them although you can turn this off using a parameter.
If you choose to not encrypt your values and create cookies with the plain text value then you can pass a third value of True
or False
. You can also be more explicit if you like:
All cookies are set as session cookies. This means that when the user closes out the browser completely, all cookies will be deleted.
This will set a cookie thats expires 5 minutes from the current time.
Again, as a security measure, all cookies automatically are set with the HttpOnly
flag which makes it unavailable to any Javascript code. You can turn this off:
This will now allow Javascript to read the cookie.
You can get all the cookies set from the browser
You can get a specific cookie set from the browser
Again, all cookies are encrypted by default so if you set a cookie with encryption then this method will decrypt the cookie. If you set a cookie in plain text then you should pass the False
as the second parameter here to tell Masonite not to decrypt your plain text cookie value.:
This will return the plain text version of the cookie.
If Masonite attempts to decrypt a cookie but cannot then Masonite will assume that the secret key that encrypted it was changed or the cookie has been tampered with and will delete the cookie completely.
If your secret key has been compromised then you may change the key at anytime and all cookies set on your server will be removed.
You may also delete a cookie. This will remove it from the browser.
You can also get the current user from the request. This requires the LoadUserMiddleware
middleware which is in Masonite by default. This will return an instance of the current user.
You can also get a route URL via the route name. Let's say we have a route like this:
We can get the URL from the route name like so:
if we have route parameters like this:
then we can pass in a dictionary:
You may also pass a list if that makes more sense to you:
This will inject that value for each parameter in order. For example if we have this route:
then we can use:
We can also check if a route contains a specific pattern:
You can also use this in a template and pass in a show
parameter to return a string instead. This is useful if you want to show active status classes depending on the current route:
We can get the current url with:
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 use the name parameter on the redirect method:
You can also redirect to a specific controller. This will find the URL that is attached to the controller method
Sometimes your routes may require parameters passed to it such as redirecting to a route that has a url like: /url/@firstname:string/@lastname:string
.
Redirecting to a named route with URL parameters:
Redirecting to a url in your application with URL parameters:
There will be plenty of times you will need to redirect back to where the user came just came from. In order for this work we will need to specify where we need to go back to. We can use the back method for this.
Masonite will check for a __back
input and redirect to that route. We can specify one using the back()
view helper function:
By default the back
helper will return the current path so you can easily go back to the previous page after the form is submitted.
You can also specify a path to go back to:
After submitting the form you can redirect back to wherever the back
template method was pointing to using the back()
method:
This will also flash the current inputs to the session. You can then get the inputs using the {{ old('key') }}
template helper:
You can redirect back with validation error message or redirect back with input value:
When redirecting back there are times where you will also want to flash the inputs to the session. With this you can simply use the back()
method but if you want a bit more control you can use the with_input()
method.
You can then get the inputs using the {{ old('key') }}
template helper:
We can also specify a default route just in case a form submitted does not specify one using a form helper:
This will check for the __back
input and if it doesn't exist it will use this default route. This is the same as a redirect if you don't use the back()
helper.
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 for testing and development purposes.
You can also get and set any headers that the request has.
You can get all WSGI information by printing:
This will print the environment setup by the WSGI server. Use this for development purposes.
You can also get a specific header:
This will return whatever the HTTP_AUTHORIZATION
header if one exists. If that does not exist then the AUTHORIZATION
header will be returned. If that does not exist then None
will be returned.
We can also set headers:
Masonite will automatically prepend a HTTP_
to the header being set for standards purposes so this will set the HTTP_AUTHORIZATION
header. If you do not want the HTTP
prefix then pass a third parameter:
This will set the AUTHORIZATION
header instead of the HTTP_AUTHORIZATION
header.
You can also set headers with a dictionary:
Masonite will set a status code of 404 Not Found
at the beginning of every request. If the status code is not changed throughout the code, either through the developer or third party packages, as it passes through each Service Provider then the status code will continue to be 404 Not Found
when the output is generated. You do not have to explicitly specify this as the framework itself handles status codes. If a route matches and your controller method is about to be hit then Masonite will set 200 OK
and hit your route. This allows Masonite to specify a good status code but also allows you to change it again inside your controller method.
You could change this status code in either any of your controllers or even a third party package via a Service Provider.
For example, the Masonite Entry package sets certain status codes upon certain actions on an API endpoint. These can be 429 Too Many Requests
or 201 Created
. These status codes need to be set before the StartProvider
is ran so if you have a third party package that sets status codes or headers, then they will need to be placed above this Service Provider in a project.
If you are not specifying status codes in a package and simple specifying them in a controller then you can do so freely without any caveats. You can set status codes like so:
You can also use an integer which will find the correct status code for you:
This snippet is exactly the same as the string based snippet above.
This will set the correct status code before the output is sent to the browser. You can look up a list of HTTP status codes from an online resource and specify any you need to. There are no limitations to which ones you can use.
You can get the request method simply:
Typically, forms only have support for GET
and POST
. You may want to change what HTTP method is used when submitting a form such as PATCH
.
This will look like:
or you can optionally use a helper method:
When the form is submitted, it will process as a PUT request instead of a POST request.
This will allow this form to hit a route like this:
There is a convenient helper method you can use the validate the request. You can import the Validator
class and use validation like so:
Masonite tries to make static files extremely easy and comes with whitenoise out of the box. Whitenoise wraps the WSGI application and listens for certain URI requests that can be resistered in your configuration files.
All configurations that are specific to static files can be found in config/storage.py
. In this file you'll find a constant file called STATICFILES
which is simply a dictionary of directories as keys and aliases as the value.
The directories to include as keys is simply the location of your static file locations. For example, if your css files are in storage/assets/css
then put that folder location as the key. For the value, put the alias you want to use in your templates. For this example, we will use css/
as the alias.
For this setup, our STATICFILES
constant should look like:
Now in our templates we can use:
Which will get the storage/assets/css/style.css
file.
All templates have a static function that can be used to assist in getting locations of static files. You can specify the driver and locations you want using the driver name or dot notation.
Take this for example:
this will render:
You can also make the config location a dictionary and use dot notation:
and use the dot notation like so:
Sometimes you may need to serve files that are normally in the root of your application such as a robots.txt
or manifest.json
. These files can be aliased in your STATICFILES
directory in config/storage.py
. They do not have to be in the root of your project but instead could be in a storage/root
or storage/public
directory and aliased with a simple /
.
For example a basic setup would have this as your directory:
and you can alias this in your STATICFILES
constant:
You will now be able to access localhost:8000/robots.txt
and you will have your robots.txt served correctly and it can be indexed by search engines properly.
Thats it! Static files are extremely simple. You are now a master at static files!
Masonite Routing is an extremely simple but powerful routing system that at a minimum takes a url and a controller. Masonite will take this route and match it against the requested route and execute the controller on a match.
All routes are created inside routes/web.py
and are contained in a ROUTES
constant. All routes consist of some form of HTTP route classes (like Get()
or Post()
). At the bare minimum, a route will look like:
Most of your routes will consist of a structure like this. All URI’s should have a preceding /
. Routes that should only be executed on Post requests (like a form submission) will look very similar:
Notice the controller here is a string. This is a great way to specify controllers as you do not have to import anything into your web.py
file. All imports will be done in the backend. More on controllers later.
If you wish to not use string controllers and wish to instead import your controller then you can do so by specifying the controller as well as well as only passing a reference to the method. This will look like:
It’s important here to recognize that we didn't initialize the controller or the method, we did not actually call the method. This is so Masonite can pass parameters into the constructor and method when it executes the route, typically through auto resolving dependency injection.
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.
There are several HTTP verbs you can use for routes:
Some routes may be very similar. We may have a group of routes under the same domain, uses the same middleware or even start with the same prefixes. In these instances we should group our routes together so they are more DRY and maintainable.
We can add route groups like so:
This alone is great to group routes together that are similar but in addition to this we can add specific attributes to the entire group like adding middleware:
In the case you are only using one middleware:
The , at the end of 'auth' ensures that it's treated as a tuple and not as an array of strings.
In this instance we are adding these 2 middleware to all of the routes inside the group. We have access to a couple of different methods. Feel free to use some or all of these options:
The prefix
parameter will prefix that URL to all routes in the group as well as the name
parameter. The code above will create routes like /dashboard/url1
with the name of post.create
. As well as adding the domain and middleware to the routes.
All of the options in a route group are named parameters so if you think adding a groups attribute at the end is weird you can specify them in the beginning and add the routes
parameter:
Even more awesome is the ability to nest route groups:
This will go to each layer and generate a route list essentially from the inside out. For a real world example we refactor routes from this:
into this:
This will likely be the most common way to build routes for your application.
You can also use View routes which is just a method on the normal route class:
You can use this view method with any route class.
You can also redirect right from the routes list using a Redirect
route class:
You do not have to specify the last 2 parameters. The default is a 302
response on GET
methods.
You may have noticed above that we have a Match
route class. This can match several incoming request methods. This is useful for matching a route with both PUT
and PATCH
.
The request methods are not case sensitive. They will be converted to uppercase on the backend. So ['Put', 'Patch']
will work just fine
We can name our routes so we can utilize these names later when or if we choose to redirect to them. We can specify a route name like so:
It is good convention to name your routes since route URI's can change but the name should always stay the same.
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 use a forward slash in the beginning of the controller namespace:
This can enable us to use controllers in third party packages.
You can also import the class directly and reference the method you want to use:
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 want to optionally match routes and route parameters. For example you may want to match /dashboard/user
and /dashboard/user/settings
to the same controller method. In this event you can use optional parameters which are simply replacing the @
with a ?
:
You can also set default values if the route is not hit:
Sometimes you will want to make sure that the route parameter is of a certain type. For example you may want to match a URI like /dashboard/1
but not /dashboard/joseph
. In order to do this we simply need to pass a type to our parameter. If we do not specify a type then our parameter will default to matching all alphanumeric and underscore characters.
This will match all integers but not strings. So for example it will match /dashboard/10283
and not /dashboard/joseph
If we want to match all strings but not integers we can pass:
This will match /dashboard/joseph
and not /dashboard/128372
. Currently only the integer and string types are supported.
These are called "Route Compilers" because they compile the route differently depending on what is specified. If you specify :int
or :integer
it will compile to a different regex than if you specified :string
.
We can add route compilers to our project by specifying them in a Service Provider.
Make sure you add them in a Service Provider where wsgi
is False
. We can add them on the Route class from the container using the compile
method. A completed example might look something like this:
We just need to call the compile()
method on the Route
class and make sure we specify a regex string by preceding an r
to the beginning of the string.
Your regex should be encapsulated in a group. If you are not familiar with regex, this basically just means that your regex pattern should be inside parenthesis like the example above.
You may wish to only render routes if they are on a specific subdomain. For example you may want example.com/dashboard
to route to a different controller than joseph.example.com/dashboard
.
Out of the box this feature will not work and is turned off by default. We will need to add a call on the Request class in order to activate subdomains. We can do this in the boot method of one of our Service Providers that has wsgi=False:
To use subdomains we can use the .domain()
method on our routes like so:
This route will match to joseph.example.com/dashboard
but not to example.com/dashboard
or test.example.com/dashboard
.
It may be much more common to match any subdomain. For this we can pass in an asterisk instead.
This will match all subdomains such as test.example.com/dashboard
, joseph.example.com/dashboard
but not example.com/dashboard
.
If a match is found, it will also add a subdomain
parameter to the Request class. We can retrieve the current subdomain like so:
Views contain all the HTML that you’re application will use to render to the user. Unlike Django, views in Masonite are your HTML templates. All views are located inside the resources/templates
directory.
All views are rendered with Jinja2 so we can use all the Jinja2 code you are used to. An example view looks like:
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
.
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.
Some views may not reside in the resources/templates
directory and may even reside in third party packages such as a dashboard package. We can locate these views by passing a /
in front of our view.
For example as a use case we might pip install a package:
and then be directed or required to return one of their views:
This will look inside the dashboard.views
package for a dashboard.html
file and return that. You can obviously pass in data as usual.
Caveats
It's important to note that if you are building a third party package that integrates with Masonite that you place any .html
files inside a Python package instead of directly inside a module. For example, you should place .html files inside a file structure that looks like:
ensuring there is a __init__.py
file. This is a Jinja2 limitation that says that all templates should be located in packages.
Accessing a global view such as:
will perform an absolute import for your Masonite project. For example it will locate:
Or find the package in a completely separate third part module. So if you are making a package for Masonite then keep this in mind of where you should put your templates.
Most of the time we’ll need to pass in data to our views. This data is passed in with a dictionary that contains a key which is the variable with the corresponding value. We can pass data to the function like so:
This will send a variable named id
to the view which can then be rendered like:
Masonite also enables Jinja2 Line Statements by default which allows you to write syntax the normal way:
Or using line statements with the @
character:
The choice is yours on what you would like to use but keep in mind that line statements need to use only that line. Nothing can be after after or before the line.
You can also add Jinja2 environments to the container which will be available for use in your views. This is typically done for third party packages such as Masonite Dashboard. You can extend views in a Service Provider in the boot method. Make sure the Service Provider has the wsgi
attribute set to False
. This way the specific Service Provider will not keep adding the environment on every request.
By default the environment will be added using the PackageLoader Jinja2 loader but you can explicitly set which loader you want to use:
The default loader of PackageLoader
will work for most cases but if it doesn't work for your use case, you may need to change the Jinja2 loader type.
If using a /
doesn't seem as clean to you, you can also optionally use dots:
if you want to use a global view you still need to use the first /
:
There are quite a few built in helpers in your views. Here is an extensive list of all view helpers:
You can get the request class:
You can get the location of static assets:
If you have a configuration file like this:
this will render:
You can create a CSRF token hidden field to be used with forms:
You can get only the token that generates. This is useful for JS frontends where you need to pass a CSRF token to the backend for an AJAX call
You can also get the current authenticated user. This is the same as doing request.user()
.
On forms you can typically only have either a GET or a POST because of the nature of html. With Masonite you can use a helper to submit forms with PUT or DELETE
This will now submit this form as a PUT request.
You can get a route by it's name by using this method:
If your route contains variables you need to pass then you can supply a dictionary as the second argument.
or a list:
Another cool feature is that if the current route already contains the correct dictionary then you do not have to pass a second parameter. For example if you have a 2 routes like:
If you are accessing these 2 routes then the @id parameter will be stored on the user object. So instead of doing this:
You can just leave it out completely since the id
key is already stored on the request object:
This is useful for redirecting back to the previous page. If you supply this helper then the request.back() method will go to this endpoint. It's typically good to use this to go back to a page after a form is submitted with errors:
Now when a form is submitted and you want to send the user back then in your controller you just have to do:
The request.back()
method will also flash the current inputs to the session so you can get them when you land back on your template. You can get these values by using the old()
method:
You can access the session here:
You can sign things using your secret token:
You can also unsign already signed string:
This is just an alias for sign
This is just an alias for unsign
This allows you to easily fetch configuration values in your templates:
Allows you to fetch values from objects that may or may not be None. Instead of doing something like:
You can use this helper:
This is the normal dd helper you use in your controllers
You can use this helper to quickly add a hidden field
Check if a template exists
Gets a cookie:
Get the URL to a location:
Below are some examples of the Jinja2 syntax which Masonite uses to build views.
It's important to note that Jinja2 statements can be rewritten with line statements and line statements are preferred in Masonite. In comparison to Jinja2 line statements evaluate the whole line, thus the name line statement.
So Jinja2 syntax looks like this:
This can be rewritten like this with line statement syntax:
It's important to note though that these are line statements. Meaning nothing else can be on the line when doing these. For example you CANNOT do this:
But you could achieve that with the regular formatting:
Whichever syntax you choose is up to you.
You can show variable text by using {{ }}
characters:
If statements are similar to python but require an endif!
Line Statements:
Using alternative Jinja2 syntax:
For loop look similar to the regular python syntax.
Line Statements:
Using alternative Jinja2 syntax:
An include statement is useful for including other templates.
Line Statements:
Using alternative Jinja2 syntax:
Any place you have repeating code you can break out and put it into an include template. These templates will have access to all variables in the current template.
This is useful for having a child template extend a parent template. There can only be 1 extends per template:
Line Statements:
Using alternative Jinja2 syntax:
Blocks are sections of code that can be used as placeholders for a parent template. These are only useful when used with the extends
above. The "base.html" template is the parent template and contains blocks, which are defined in the child template "blocks.html".
Line Statements:
Using alternative Jinja2 syntax:
As you see blocks are fundamental and can be defined with Jinja2 and line statements. It allows you to structure your templates and have less repeating code.
The blocks defined in the child template will be passed to the parent template.
You can do a lot of other awesome things like rule enclosures. Read more under the
Here is an example application that is being upgraded from 2.1 to 2.2
This might look magical to you so be sure to read about the IOC container in the documentation.
Read about how to create and use views by reading the documentation
Read more about helper functions in the documentation.
Read more about how to use and create middleware in the documentation.
If this looks weird to you or you are not sure how the container integrates with Masonite, make sure you read the documentation
Remember that by passing in parameters like Request
to the controller method, we can retrieve objects from the IOC container. Read more about the IOC container in the documentation.
Views use for it's template rendering. You can read about Jinja2 at the .
This section requires knowledge of and how the works. Be sure to read those documentation articles.
Learn more about session in the documentation.
It's extremely simple to add commands to Masonite via the craft command tool and Service Providers. If you have been using Masonite for any amount of time you will learn that commands are a huge part of developing web applications with Masonite. We have made it extremely easy to create these commands and add them to craft to build really fast personal commands that you might use often.
Masonite uses the Cleo package for creating and consuming commands so for more extensive documentation on how to utilize commands themselves, how to get arguments and options, and how to print colorful text to the command line.
Read more about Cleo by visiting the Cleo Documentation.
You can create commands by using craft itself:
This will create a app/commands/HelloCommand.py
file with boilerplate code that looks like this:
Let's create a simple hello name application which prints "hello your-name" to the console.
Where it says command:name
inside the docstring we can put hello
and inside the argument we can put name
like so:
Inside the handle
method we can get the argument passed by specifying self.argument('name')
. Simply put:
That's it! Now we just have to add it to our craft command.
We can add commands to craft by creating a Service Provider and registering our command into the container. Craft will automatically run all the register methods on all containers and retrieve all the commands.
Let's create a Service Provider:
This will create a provider in app/providers/HelloProvider.py
that looks like:
Let's import our command and register it into the container. Also because we are only registering things into the container, we can set wsgi = False
so it is not ran on every request and only before the server starts:
Make sure you instantiate the command. Also the command name needs to end in "Command". So binding HelloCommand
will work but binding Hello
will not. Craft will only pick up commands that end in Command
. This is also case sensitive so make sure Command
is capitalized.
Like normal, we need to add our Service Provider to the PROVIDERS
list inside our config/providers.py
file:
That's it! Now if we run:
We will see our new hello
command:
and if we run:
We will see an output of:
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 dictionary where classes are loaded into it by key-value pairs, and then can be retrieved by either the key or value through resolving objects. That's it.
Think of "resolving objects" as Masonite saying "what does your object need? Ok, I have them in this dictionary, let me get them for you."
The container holds all of the frameworks classes and features so adding features to Masonite only entails adding classes into the container to be used by the developer later on. This typically means "registering" these classes into the container (more about this later on).
This allows Masonite to be extremely modular.
There are a few objects that are resolved by the container by default. These include your controller methods (which are the most common and you have probably used them so far) driver and middleware constructors and any other classes that are specified in the documentation.
There are four methods that are important in interacting with the container: bind
, make
and resolve
In order to bind classes into the container, we will just need to use a simple bind
method on our app
container. In a service provider, that will look like:
This will load the key value pair in the providers
dictionary in the container. The dictionary after this call will look like:
The service container is available in the Request
object and can be retrieved by:
Sometimes you really don't care what the key is for the object you are binding. For example you may be binding a Markdown
class into the container but really don't care what the key binding is called. This is a great reason to use simple binding which will set the key as the object class:
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.
You can bind singletons into the container. This will resolve the object at the time of binding. This will allow the same object to be used throughout the lifetime of the server.
You can also check if a key exists in the container by using the has
method:
You can also check if a key exists in the container by using the in
keyword.
You may want to collect specific kinds of objects from the container based on the key. For example we may want all objects that start with "Exception" and end with "Hook" or want all keys that end with "ExceptionHook" if we are building an exception handler.
We can easily collect all objects based on a key:
This will return a dictionary of all objects that are binded to the container that start with anything and end with "ExceptionHook" such as "SentryExceptionHook" or "AwesomeExceptionHook".
We can also do the opposite and collect everything that starts with a specific key:
This will collect all keys that start with "Sentry" such as "SentryWebhook" or "SentryExceptionHandler."
Lastly, we may want to collect things that start with "Sentry" and end with "Hook"
This will get keys like "SentryExceptionHook" and "SentryHandlerHook"
You can also collect all subclasses of an object. You may use this if you want to collect all instances of a specific class from the container:
This is the most useful part of the container. It is possible to retrieve objects from the container by simply passing them into the parameter list of any object. Certain aspects of Masonite are resolved such as controller methods, middleware and drivers.
For example, we can hint that we want to get the Request
class and put it into our controller. All controller methods are resolved by the container. Masonite 2.1 only supports annotations resolving by default:
In this example, before the show method is called, Masonite will look at the parameters and look inside the container for the Request object.
Masonite will know that you are trying to get the Request
class and will actually retrieve that class from the container. Masonite will search the container for a Request
class regardless of what the key is in the container, retrieve it, and inject it into the controller method. Effectively creating an IOC container with dependency injection. capabilities Think of this as a get by value instead of a get by key like the earlier example.
Pretty powerful stuff, eh?
Another powerful feature of the container is it can actually return instances of classes you annotate. For example, all Upload
drivers inherit from the UploadContract
which simply acts as an interface for all Upload
drivers. Many programming paradigms say that developers should code to an interface instead of an implementation so Masonite allows instances of classes to be returned for this specific use case.
Take this example:
Notice that we passed in a contract instead of the upload class. Masonite went into the container and fetched a class with the instance of the contract.
This feature should not be used and you should instead use the more explicit form of resolving in the section above.
You can technically still resolve parameters with your container like you could in previous versions of Masonite. Resolving a parameter looked like this:
Although this was removed in 2.1+, you may still enable it on a per project basis. To enable it, go to your wsgi.py
file and add this to the constructor of your App class towards the top of the file:
Your project will now resolve parameters as well. Resolving parameters looks for the key in the container instead of the class.
The service container can also be used outside of the flow of Masonite. Masonite takes in a function or class method, and resolves it's dependencies by finding them in the service container and injecting them for you.
Because of this, you can resolve any of your own classes or functions.
Remember not to call it and only reference the function. The Service Container needs to inject dependencies into the object so it requires a reference and not a callable.
This will fetch all of the parameters of randomFunction
and retrieve them from the service container. There probably won't be many times you'll have to resolve your own code but the option is there.
Sometimes you may wish to resolve your code in addition to passing in variables within the same parameter list. For example you may want to have 3 parameters like this:
You can resolve and pass parameter at the same time by adding them to the resolve()
method:
Masonite will go through each parameter list and resolve them, if it does not find the parameter it will pull it from the other parameters specified. These parameters can be in any order.
If you need to utilize a container outside the normal flow of Masonite like inside a command then you can import the container directly.
This would look something like:
Sometimes when you resolve an object or class, you want a different value to be returned.
We can pass a simple value as the second parameter to the swap
method which will be returned instead of the object being resolved. For example this is used currently when resolving the Mail
class like this:
but the class definition for the Mail
class here looks like this:
How does it know to resolve the smtp driver instead? It's because we added a container swap. Container swaps are simple, they take the object as the first parameter and either a value or a callable as the second.
For example we may want to mock the functionality above by doing something like this in the boot method of a Service Provider:
Notice that we specified which class should be returned whenever we resolve the Mail
class. In this case we want to resolve the default driver specified in the projects configurations.
Instead of directly passing in a value as the second parameter we can pass in a callable instead. The callable MUST take 2 parameters. The first parameter will be the annotation we are trying to resolve and the second will be the container itself. Here is an example of how the above would work with a callable:
Notice that the second parameter is a callable object. This means that it will be called whenever we try to resolve the Mail
class.
Remember: If the second parameter is a callable, it will be called. If it is a value, it will simply be returned instead of the resolving object.
Sometimes we might want to run some code when things happen inside our container. For example we might want to run some arbitrary function about we resolve the Request object from the container or we might want to bind some values to a View class anytime we bind a Response to the container. This is excellent for testing purposes if we want to bind a user object to the request whenever it is resolved.
We have three options: on_bind
, on_make
, on_resolve
. All we need for the first option is the key or object we want to bind the hook to, and the second option will be a function that takes two arguments. The first argument is the object in question and the second argument is the whole container.
The code might look something like this:
Notice that we create a function that accepts two values, the object we are dealing with and the container. Then whenever we run on_make
, the function is ran.
We can also bind to specific objects instead of keys:
This then calls the same attribute but anytime the Request
object itself is made from the container. Notice everything is the same except line 6 where we are using an object instead of a string.
We can do the same thing with the other options:
By default, Masonite will not care if you override objects from the container. In other words you can do this:
Without issue. Notice we are binding twice to the same key. You can change this behavior by specifying 2 values in the constructor of the App
class:
If override is False
, it will not override values in the container. It will simply ignore them if you are trying to bind twice to the same key. If override is True
, which it is by default, you will be allowed to override keys in the container with new values by binding them.
Strict will throw an exception if you try binding a key to the container. So with override being False
it simply ignored binding a key to the container that already exists, setting strict to True
will actually throw an exception.
Container remembering is an awesome feature where the container will remember the objects you passed in and instead of resolving the object over and over again which could possibly be expensive, it will pass in the previous objects that were passed in by storing and retrieving them from a separate remembering list the container builds.
Once an object is resolved for the first time, a new dictionary is built containing that object and it's dependencies. The second time that object is resolved, instead of inspecting the object to see which dependencies it should pass in, Masonite will inject the ones from the remembering dictionary.
This can speed up resolving your code by 10 - 15x. A significant speed improvement.
You can enable this on a per application basis by setting the remembering
parameter to True
on your container class. This can be found in your wsgi.py
file:
Masonite has a Service Container which allows you to add objects into the container and have them auto resolved in controllers and other classes. This is an excellent feature and what makes Masonite so powerful. The most obvious way to load classes into the container is through creating a Service Provider and interacting with the container from there.
With Masonite 2, we can use the builtin autoloader in order to load classes into the container in a much simpler way.
The configuration variable for autoloading is inside the config/application.py
file which contains a list of directories:
Out of the box, Masonite will autoload all classes that are located in the app
directory which unsurprisingly contains all of the application models.
Masonite will go through each directory listed and convert it to a module. For example if given the directory of app/models
it will convert that to app.models
and fetch that module. It will use inspection to go through the entire module and extract all classes imported or defined.
If your code looks something like:
Then the autoloader will fetch three classes: the belongs_to
class, the Model
class and the User
class. The autoloader will then check if the module of the classes fetched are actually apart of the module being autoloaded.
In other words the modules of the above classes are: orator.orm
, config.database
and app
respectively. Remember that we are just autoloaded the app
module so it will only bind the app.User
class to the container with a binding of the class name: User
and the actual object itself: <class app.User.User>
.
All of this autoloading is done when the server is first started but before the WSGI server is ready to start accepting requests so there are no performance hits for this.
Since the app directory is autoloaded, and our User
model is in that directory, the User
model will be loaded into the container when the server starts.
All bindings into the Service Container will be the name of the object as the key and the actual object as the value. So the User
model will be accessed like:
There is no reason to add models into the container unless your specific use case requires it but this is just an example.
You don't have to keep your models in the app
directory. Feel free to move them anywhere but they will not be autoloaded outside the app directory by default. In order to autoload other directories we can add them to the AUTOLOAD
variable.
For example if we have an app directory structure like:
Then we can edit our AUTOLOAD
variable like so:
And then be able to do:
Being that the container is useful as an IOC container, another use case would be if a third party library needed some models to manipulate and then bind them back into the container. An example of this type of library would be one that needs to change the models methods in order to capture query operations and send them to a dashboard or a report.
The autoload class will raise a few exceptions so you should be aware of them in order to avoid confusion when these exceptions are raised.
This exception will be thrown if any of your autoload paths end with a forward slash like the first element in this list:
Notice the path is now app/ and not app. This will throw an exception when the server first starts.
This exception will be thrown when one of your classes are about to overwrite a container binding that is outside of your search path. The search path being the directories you specified in the AUTOLOAD
constant.
For example, you may have a model called Request
like so:
Without this exception, your application will overwrite the binding of the Masonite Request
class.
When Masonite goes to autoload these classes, it will detect that the Request
key has already been bound into the container (by Masonite itself). Masonite will then detect if that Request
object in the container is within the search path. In other words it will check for a Request
class inside the current module you are autoloading.
If the object is outside of the module you are autoloading then it will throw this exception. In this instance, it will throw an exception because the Request
key in the container is the <class masonite.request.Request> class which is outside of the app
module (and inside the masonite.request
module).
If you find yourself hitting this exception then move the object outside of a directory being autoloaded and into a separate directory outside of the autoloader and then you can manually bind into the container with a different key, or simply rename the class to something else. When using models, you can rename the model to whatever you like and then specify a __table__
attribute to connect the model to the specific table.
Although it is useful to get the model by the actual container key name, it might not be as practical or even the best way to fetch models from the container.
The recommended approach is to simply fetch the class itself by using annotations so you can adjust the variable name and ensure consistency throughout your application.
If this seems like a strange syntax to you, be sure to read the Resolve section of the Service Container documentation.
You may also want to autoload classes yourself. This may be useful if building a package and needing to get all classes from a certain location or even all instances from a certain directory.
This might in useful in the Masonite scheduling feature to grab all the classes that are subclasses of the Task
class.
Autoloading class instances could look something like:
This will fetch all the classes in the app/models
directory that are instances of the Model
class. The classes
attribute contains a dictionary of all the classes it found.
If you don't want to get classes that are instances of another class then we can simply collect classes:
You may have noticed that this autoload class will actually capture all classes found. These classes include the classes that were imported as well.
The default autoload in the config file only loads application specific classes.
We can only get application specific classes by passing a parameter in:
This will check the namespace on the class found and make sure it is apart of the path that is being searched.
The classes that the autoloader finds are just classes themselves which are uninstantiated. This is great for fetching models but won't be good for fetching other objects like commands. Commands need to be instantiated when they go into the container by design so they can be found by Masonite and cleo.
We can tell the autoloader to instantiate the class by passing another parameter. This will simply instantiate the object without passing any parameters in.
If your class needs parameters then you should collect the classes and instantiate each one individually.
You can see that now all the objects found are instantiated.
The craft command tool is a powerful developer tool that lets you quickly scaffold your project with models, controllers, views, commands, providers, etc. which will condense nearly everything down to its simplest form via the craft namespace. No more redundancy in your development time creating boilerplate code. Masonite condenses all common development tasks into a single namespace.
For example, In Django you may need to do something like:
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.
When craft is used outside of masonite directory, it will only show a few commands such as the new
and install
commands. Other commands such as commands for creating controllers or models are loaded in from the Masonite project itself.
Many commands are loaded into the framework itself and fetched when craft is ran in a Masonite project directory. This allows version specific Masonite commands to be efficiently handled on each subsequent version as well as third party commands to be loaded in which expands craft itself.
The possible commands for craft include:
You can "tinker" around with Masonite by running:
This command will start a Python shell but also imports the container by default. So we can call:
And play around the container. This is a useful debugging tool to verify that objects are loaded into the container if there are any issues.
Another useful command is the show:routes command which will display a table of available routes that can be hit:
This will display a table that looks like:
If you are trying to debug your application or need help in the Slack channel, it might be beneficial to see some useful information information about your system and environment. In this case we have a simple command:
This will give some small details about the current system which could be useful to someone trying to help you. Running the command will give you something like this:
Feel free to contribute any additional information you think is necessary to the command in the core repository.
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.
Validators are classes based on validating form or request input. We can create validators by running:
Be sure to read the Validation documentation to learn more about validators.
Masonite uses Orator which is an active record style ORM. If you are coming from other Python frameworks you may be more familiar with Data Mapper ORM's like Django ORM or SQLAlchemy. These style ORM's are useful since the names of the column in your table are typically the names of class attributes. If you forget what you named your column you can typically just look at the model but if your model looks something like:
Then it is not apparent what the tables are. We can run a simple command though to generate a docstring that we can throw onto our model:
Which will generate something like this in the terminal:
We can now copy and paste that onto your model and change whatever we need to:
You can also specify the connection to use using the --connection
option.
If you wish to scaffold a controller, just run:
This command will create a new controller under app/http/controllers/DashboardController.py
. By convention, all controllers should have an appended “Controller” and therefore Masonite will append "Controller" to the controller created.
You can create a controller without appending "Controller" to the end by running:
This will create a app/http/controllers/Dashboard.py file with a Dashboard controller. Notice that "Controller" is not appended.
-e
is short for --exact
. Either flag will work.
You may also create resource controllers which include standard resource actions such as show, create, update, etc:
-r
is short for --resource
. Either flag will work.
You can also obviously combine them:
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 boilerplate of a .env
file. In order to install the dependencies, as well as copy the example environment file to a .env
file, just run:
The craft install
command will also run craft key --store
as well which generates a secret key and places it in the .env
file.
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 essentially 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.
You can also use the -s and -m flags to create a seed or model at the same time.
This is a shortcut for these 3 commands:
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.
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.
Masonite has a queue feature that allows you to load the jobs you create in the section above and run them either asyncronously or send them off to a message broker like RabbitMQ. You may start the worker by running:
You'll need to read the documentation in the Queues and Jobs documentation for futher info on how to setup this feature.
You may create a PyPi package with an added integrations.py
file which is specific to Masonite. You can learn more about packages by reading the Creating Packages documentation. To create a package boilerplate, just run:
You can scaffold out basic command boilerplate:
This will create a app/commands/HelloCommand.py
file with the HelloCommand
class.
You can run the WSGI server by simply running:
This will set the server to auto-reload when you make file changes.
You can also set it to not auto-reload on file changes:
or the shorthand
If you have unmigrated migrations, Masonite will recommend running craft migrate
when running the server.
The serve command also has a live reloading option which will refresh any connected browsers so you can more quickly prototype your jinja templates.
This is not a great tool for changing Python code and any Python code changes may still require a browser refresh to see the changes.
You can bind to a host using -b
and/or a port using -p
Masonite comes with a pretty quick auto reloading development server. By default there will be a 1 second delay between file change detection and the server reloading. This is a fairly slow reloading interval and most systems can handle a much faster interval while still properly managing the starting and killing of process PID's.
You can change the interval to something less then 1 using the -i
option:
You will notice a considerably faster reloading time on your machine. If your machine can handle this interval speed (meaning your machine is properly starting and killing the processes) then you can safely proceed using a lower interval during development.
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.
You may also pass the --store
flag which will automatically add the key to your .env
file:
This command is ran whenever you run craft install
Great! You are now a master at the craft command line tool.
You can also scaffold out basic test cases. You can do this by running:
This will scaffold the test in the base tests/
directory and you can move it to wherever you like from there.
Masonite has the concept of publishing where you can publish specific assets to a developers application. This will allow them to make tweaks to better fit your package to their needs.
You can publish a specific provider by running:
You can also optionally specify a tag:
You should read more about publishing at the publishing documentation page.
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 Service Container documentation. If you look inside the config/providers.py
file, you will find a PROVIDERS
list which contains all the Service Providers involved in building the framework.
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 by simply using a craft command:
This will create a new Service Provider under our app/providers/DashboardProvider.py
. This new Service Provider will have two simple methods, a register
method and a boot
method. We'll explain both in detail.
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 the boot method 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.
Service providers have several methods we can use to help us bind objects into the container.
We can simply bind commands into the container:
We can also bind http and route middleware into the container:
Notice that the route middleware accepts a dictionary and the http middleware accepts a list
We can add directories that have migrations easily as well:
We can also add routes:
We can also add any directories that have asset files:
Great! It's really that simple. Just this knowledge will take you a long way. Take a look at the other service providers to get some inspiration on how you should create yours. Again, if you do create a Service Provider, consider making it available on PyPi so others can install it into their framework.
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.
Masonite is bootstrapped via Service Providers, these providers load features, hooks, drivers and other objects into the Service Container which can then be pulled out by you, the developer, and used in your views, middleware and drivers.
With that being said, not all Service Providers need to be ran on every request and there are good times to load objects into the container. For example, loading routes into the container does not need to be ran on every request. Mainly because they won't change before the server is restarted again.
Now the entry point when the server is first ran (with something like craft serve
) is the wsgi.py
file in the root of the directory. In this directory, all Service Providers are registered. This means that objects are loaded into the container first that typically need to be used by any or all Service Providers later. All Service Providers are registered regardless on whether they require the server to be running (more on this later).
Now it's important to note that the server is not yet running we are still in the wsgi.py
file but we have only hit this file, created the container, and registered our Service Providers.
Right after we register all of our Service Providers, we break apart the provider list into two separate lists. The first list is called the WSGIProviders
list which are providers where wsgi=True
(which they are by default). We will use this list of a smaller amount of providers in order to speed up the application since now we won't need to run through all providers and see which ones need to run.
While we are in the loop we also create a list of providers where wsgi=False
and boot those providers. These boot methods may contain things like Manager classes creating drivers which require all drivers to be registered first but doesn't require the WSGI server to be running.
Also, more importantly, the WSGI key is binded into the container at this time. The default behavior is to wrap the WSGI application in a Whitenoise container to assist in the straight forwardness of static files.
This behavior can be changed by swapping that Service Provider with a different one if you do not want to use Whitenoise.
Once all the register methods are ran and all the boot methods of Service Providers where wsgi is false, and we have a WSGI key in the container, we can startup the server by using the value of the WSGI key.
We then make an instance of the WSGI key from the container and set it to an application variable in order to better show what is happening. Then this is where the WSGI server is started.
Now that we have the server running, we have a new entry point for our requests. This entry point is the app function inside bootstrap/start.py.
Now all wsgi servers set a variable called environ. In order for our Service Providers to handle this, we bind it into the container to the Environ key.
Next we run all of our Service Providers where wsgi is true now (because the WSGI server is running).
The Request Life Cycle is now going to hit all of these providers. Although you can obviously add any Service Providers you at any point in the request, Masonite comes with 5 providers that should remain in the order they are in. These providers have been commented as # Framework Providers
. Because the request needs to hit each of these in succession, they should be in order although you may put any amount of any kind of Service Providers in between them.
We enter into a loop with these 5 Service Providers and they are the:
This Service Provider registered objects like the routes, request class, response, status codes etc into the container and then loads the environment into these classes on every request so that they can change with the environment. Remember that we registered these classes when the server first started booting up so they remain the same class object as essentially act as singletons Although they aren't being reinstantiated with the already existing object, they are instantiated once and die when the server is killed.
If one of the middleware has instructed the request object to redirect, the view that is ready to execute, will not execute.
For example, if the user is planned on going to the dashboard view, but middleware has told the request to redirect to the login page instead then the dashboard view, and therefore the controller, will not execute at all. It will be skipped over. Masonite checks if the request is redirecting before executing a view.
Also, the request object can be injected into middleware by passing the Request
parameter into the constructor like so:
This will inject the Request
class into the constructor when that middleware is executed. Read more about how middleware works in the Middleware documentation.
This provider loads the ability to use sessions, adds a session helper to all views and even attaches a session attribute to the request class.
This provider takes the routes that are loaded in and makes the response object, static codes, runs middleware and other route displaying specific logic. This is the largest Service Provider with the most logic. This provider also searches through the routes, finds which one to hit and exectues the controller and controller method.
This provider is responsible for showing the nice HTTP status codes you see during development and production. This Service Provider also allows custom HTTP error pages by putting them into the resources/templates/errors
directory.
Nothing too special about this Service Provide. You can remove this if you want it to show the default WSGI server error.
This Service Provider collects a few classes that have been manipulated by the above Service Providers and constructs a few headers such as the content type, status code, setting cookies and location if redirecting.
Once these 5 providers have been hit (and any you add), we have enough information to show the output. We leave the Service Provider loop and set the response and output which are specific to WSGI. The output is then sent to the browser with any cookies to set, any new headers, the response, status code, and everything else you need to display html (or json) to the browser.
Status codes are a crucial part of any application. They allow your users to identify exactly what went wrong with your site without showing too much information. By default, Masonite will show generic 404 pages when a url is missed.
When APP_DEBUG is True in your .env file, an exception view is shown to help you debug your application. When it is False then it will show a generic 500 error page.
This behavior is through the StatusCodeProvider
in your PROVIDERS
list. In addition to this behavior we can also show our own error pages.
Masonite will first look for the error that is being thrown in your resources/templates/errors
directory and render that template. If one does not exist then it will return a generic view from the Masonite package itself.
For example if a 404 Not Found error will be thrown then it will first check in resources/templates/errors/404.html
and render that template. This is the same behavior for 500 Server Not Found
errors and other errors thrown.
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 contain a before
method and/or an after
method.
There are four types of middleware in total:
Middleware ran before every request
Middleware ran after every request
Middleware ran before certain routes
Middleware ran after certain routes
There are what Masonite calls HTTP Middleware which are middleware designed to run on every request and Route Middleware which is middleware designed to only be called on certain requests, such as checking if a user is logged in.
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:
Route middleware have the unique option of also being stacks of middleware (or lists, really). So we can specify a middleware to have a list of middleware instead of one string based middleware:
Notice that we can use both lists and strings for middleware. As a list, all the middleware in the list will run when we call the auth
middleware stack.
There are several default middleware that come with Masonite. These middleware can be changed or removed to fit your application better.
This middleware is design to redirect users to the login page if they are not logged in. This is loaded as a Route Middleware inside config/middleware.py
and design to only be ran on specific routes you specify.
You can run this middleware on any route by specifying the key in the middleware method on your route:
This middleware checks to see if the logged in user has verified their email. If they haven't it will redirect the user to a page reminding them to verify their email.
You can run this middleware on any route by specifying the key in the middleware method on your route:
This middleware is an HTTP Middleware and runs on every request. This middleware checks for the CSRF token on POST
requests. For GET
requests, Masonite generates a new token. The default behavior is good for most applications but you may change this behavior to suit your application better.
This middleware checks if the user is logged in and if so, loads the user into the request object. This enables you to use something like:
This middleware will convert dictionary responses into JSON responses
This middleware will take the response provided and add the necessary headers to the response
You can pass parameters from your routes to your middleware in cases where a middleware should act differently depending on your route.
You can do this with a :
symbol next to your route middleware name and then pass in those parameters to the before
and after
middleware methods.
For example, we may be creating a middleware for request throttling and in our routes we have something like this:
notice the throttle:2,100 syntax. The 2 and the 100 will then be passed into the before and after methods of your middleware:
Again, middleware should live inside the app/http/middleware
folder and should look something like:
Middleware constructors are resolved by the container so simply pass in whatever you like in the parameter list and it will be injected for you.
If Masonite is running a “before” middleware, that is middleware that should be ran before the request, Masonite will check all middleware and look for a before
method and execute that. The same for “after” middleware.
You may exclude either method if you do not wish for that middleware to run before or after.
This is a boilerplate for middleware. It's simply a class with a before and/or after method. Creating a middleware is that simple. Let's create a middleware that checks if the user is authenticated and redirect to the login page if they are not. Because we have access to the request object from the Service Container, we can do something like:
That's it! Now we just have to make sure our route picks this up. If we wanted this to execute after a request, we could use the exact same logic in the after
method instead.
Since we are not utilizing the after
method, we may exclude it all together. Masonite will check if the method exists before executing it.
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.
It's common to want to use a Service Provider to add new methods to a class. For example, you may want to add a is_authenticated
method to the Request
class. Your package and Service Provider may be for a better authentication system.
You may easily extend classes that inherit from the Extendable
class. Many of the built in classes inherit from it.
You have a few options for adding methods to any of the core classes. You can extend a class with functions, classes and class methods. Typical usage may look like:
Usage is very simple and has several options for extending a class. Notice that we don't call the function but we pass the reference to it.
This will simply add the function as a bound method to the Request
class
We can also extend a class method which will take the method given and add it as a bound method.
We can even extend a whole class which will get all the classes methods and create bound methods to the Request class.
Creating packages is very simple for Masonite. You can get a package created and on PyPi is less than 5 minutes. With Masonite packages you'll easily be able to integrate and scaffold all Masonite projects with ease. Masonite comes with several helper functions in order to create packages which can add configuration files, routes, controllers, views, commands, migrations and more.
As a developer, you will be responsible for both making packages and consuming packages. In this documentation we'll talk about both. We'll start by talking about how to make a package and then talk about how to use that package or other third party packages.
Masonite, being a Python framework, can obviously utilize all Python packages that aren’t designed for a specific framework. For example, Masonite can obviously use a library like requests but can’t use Django Rest Framework.
Similarly to how Django Rest Framework was built for Django, you can also build packages specific to Masonite. You also don't need to build for one use case or the other. You can easily build a Python package that can be used specifically for any Python package but also create a way it can wire directly into Masonite using Service Providers. We'll talk more about this later.
There are several key functions that Masonite uses in order to create applications. These include primarily: routes, controllers, views, migrations, and craft commands. Creating a package is simple. Conveniently Masonite comes with several helper functions in order to create all of these.
You can easily create a command like craft mypackage:install
and can scaffold out and install controllers, routes, etc into a Masonite project. You can also use the publish command which will look something like craft publish YourServiceProvider
. There really isn't much of a difference in terms of functionality but the install command will require you to manually copy things to where they need to go and the built in publish
command takes care of some of these things for you.
You do not have to use this functionality and instead have the developer copy and paste things that they need to from your documentation but having a great setup process is a great way to promote developer happiness which is what Masonite is all about.
The best way to create a package is to you the .
Just go to the repo and download a zip of the file. It's not beneficial to simply git clone it since you will eventually need to create your own repository and cloning the repo will still track the existing starter-package repo.
Once downloaded you can unzip it to wherever you want on your machine. From there you should create a virtual environment and run the tests:
Activating your virtual environment on Mac and Linux is simple:
or if you are on Windows:
Now you can install some development pypi packages to help you in your package development journey like flake8
and pytest
.
The starter package comes with a make file to help you get started faster. Just run:
This will install the craft CLI tool as well as some other requirement packages.
Now you can run the tests to make sure everything is working properly:
You should see all the basic setup tests passing. Now you can start your TDD flow or start building tests around your package.
The testing suite is the full Masonite testing suite so be sure to read the Testing docs.
First we will walk through how to create a simple install command. This will show you how to move things manually into the correct locations. After, we will look into using the publish
command which will automate some of these things. If you want to skip to that part you can scroll down a bit to the next section.
Head over to that documentation page and create an InstallCommand
and an InstallProvider
. This step should take less than a few minutes. Once those are created we can continue to the adding package helpers below.
Remember you have access to craft commands so you can do something like:
You'll need to move your command inside the src/package
directory but it will prevent you from having to write a lot of boiler plate while developing your package.
Just move these generated files into the src/package/commands
and src/package/providers
directories. You may have to create these directories if they don't exist.
Masonite packages allow you to add new migrations to a project. For example, this could be used to add a new package_subscriptions
table if you are building a package that works for subscribing users to Stripe.
Inside the Service Provider you plan to use for your package we can register our directory:
Masonite will find any keys in the container that end with MigrationDirectory
and will add it to the list of migrations being ran whenever craft migrate
and craft migrate:*
commands are ran. When you run migrations you will now see something like this:
Notice how we are now migrating several directories at a time rather than only from a single directory. If this is the approach you want your package to take then this is the best way to do it.
The package_directory
variable contains the absolute path to the current file so the migration directory being added should also be an absolute path to the migration directory as demonstrated here. Notice the ../migrations
syntax. This is going back one directory and into a migration directory there.
There are a few package helpers you can use to integrate your package into a Masonite application. Now we just need to put our masonite.package
helper functions in our install command. The location we put in our create_or_append_config()
function should be an absolute path location to our package.
Let's create a basic config file and then have the install command copy it over when they run the command. Let's create a simple file in src/package/snippets/configs/services.py
To help with this, Masonite has put a variable called package_directory
here. Our handle method inside our install command should look something like:
Now when you run the install command it will create or append the config to the config/services.py
file.
This will append the configuration file that has the same name as our package configuration file. In this case the configuration file we are creating or appending to is config/services.py
because our packages configuration file is services.py
. If we want to append to another configuration file we can simply change the name of our package configuration file.
We can either test our package locally or upload our package to PyPi.
To test our package locally, if you use virtual environments, just go to your Masonite project and activate your virtual environment. Navigate to the folder where you created your package and run:
If you make changes you'll need to uninstall and reinstall the package buy running something like:
This will install your new package into your virtual environment. Go back to your project root so we can run our craft package:install
command. If we run that we should have a new configuration file under config/services.py
.
So we have our package installable as well as tested and integrated nicely into an existing Masonite app. Now it's time to upload it to PYPI so everyone can install it.
We made it really easy to do this with the make commands.
If you have uploaded PYPI packages before you'll know you need a file in your home directory.
If you do not have one then you go create one using another make command:
This will move the file to your home directory. If you are using windows you may need to move this file manually.
Make sure you go to your setup.py
file and change all the package configurations. There are lots of comments next to each option so if you are unsure what it does then just give it a read.
Once you have your setup.py file looking correct you can run the make command:
This will install twine if it doesn't exist, build the package, upload it to PYPI and delete the artifacts.
Easy, right?
Now that your package is on PyPi we can just run:
Then add your Service Provider to the PROVIDERS
list:
and then run:
Remember our Service Provider added the command automatically to craft.
Again, not all packages will need to be installed or even need commands. Only packages that need to scaffold the project or something similar need one. This should be a judgment call on the package author instead of a requirement.
You will know if a package needs to be installed by reading the packages install documentation that is written by the package authors.
Masonite has the concept of publishing packages. This allows you to manage the integration with your package and Masonite in a more seamless way. Publishing allows you to add things like routes, views, migrations and commands easily into any Masonite app and it is all handled through your service provider
The goal is to have a developer run:
This should be the name of your provider class.
and have all your assets moved into the new Masonite application.
You can create or append any files you need to in a developers masonite application. This can be used for any files to include commands, routes, config files etc.
For example let's say you have a directory in your package like:
Inside our service provider we can do this:
Notice our command path is 1 directory back inside the commands
directory. We then combine the directory with the RuleCommand.py
file and tell Masonite to put it inside the app/commands/RuleCommand.py
module inside the users directory.
The user of your package will now have a new command in their application!
You can take any migrations in your package and send them to the Masonite applications migration directory. This is useful if you want to have some developers edit your custom migrations before they migrate them.
For example let's say you have a directory in your package like:
The migrations like user_migration.py
should be full migration files.
Then you can have a service provider like this:
This will create a new migration in the users directory.
You can also add tags to each of these migrations as well. For example if you have 2 sets of migrations you can do this instead:
Now a user can only either publish migrations or commands by adding a --tag
option
This will ignore the commands publishing and only publish the migrations
Most of the responses you will work with simply involve returning various data types / classes / objects in the controller method. For example, you may be used to returning a view.render()
object in the controller method. This will return a View
instance which Masonite will extract out the rendered html template from it.
Below is a list of all the responses you can return
You can simply return a string which will output the string to the browser:
This will set headers and content lengths similiar to a normal HTML response.
You can return an instance of a View
object which Masonite will then pull the HTML information that Jinja has rendered. This is the normal process of returning your templates. You can do so by type hinting the view class and using the render method:
Notice you can also pass in a dictionary as a second argument which will pass those variables to your Jinja templates.
There are a few ways to return JSON responses. The easiest way is to simply return a dictionary like this:
This will return a response with the appropriate JSON related headers.
Similiarly you can return a list:
If you are working with models then its pretty easy to return a model as a JSON response by simply returning a model. This is useful when working with single records:
This will return a response like this:
If you are working with collections you can return something similiar which will return a slightly different JSON response with several results:
Which will return a response like:
If you need to paginate a response you can return an instance of Paginator
. You can do so easily by using the paginate()
method:
The value you pass in to the paginate method is the page size or limit of results you want to return.
This will return a response like:
You can override the page size and page number by passing in the appropriate query inputs. You can change the page you are looking at by passing in a ?page=
input and you can change the amount of results per page by using the ?page_size=
input.
If you are building an API this might look like /api/users?page=2&page_size=5
. This will return 5 results on page 2 for this endpoint.
You can also return a few methods on the request class. These are mainly used for redirection.
For redirecting to a new route you can return the redirect()
method:
The response class is what Masonite uses internally but you can explicit use it if you find the need to. A need might include setting a response in a middleware or a service provider where Masonite does not handle all the response converting for you. It is typically used to condense a lot of redundant logic down throughout the framework like getting the response ready, status codes, content lengths and content types.
Previously this needed to be individually set but now the response object abstracts a lot of the logic. You will likely never need to encounter this object during normal development but it is documented here if you need to use it similarly to how we use it in core.
We can set a JSON response by using the json()
method. This simply requires a dictionary:
This will set the Content-Type
, Content-Length
, status code and the actual response for you.
Keep in mind this is the same thing as doing:
Since Masonite uses a middleware that abstracts this logic.
The view()
method either takes a View
object or a string:
You can also use some very basic URL redirection using the response object:
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!
You'll likely want to seed your database during development in order to get some dummy data into your database to start working fast.
Masonite uses Orator to generate seed files. This documentation will explain how to create the files itself.
Read more about creating seed classes with the .
We can simply create a new seeder by running:
This will create a new seeder inside the databases/seeds
directory. This will also create a database_seeder.py
file which will be where the root of all seeds should be.
The user_table_seeder
should be where you simply abstract your seeds to.
We can run:
This will run the database_seeder
seed. We can also specify an individual seed
Which will run the user_table_seeder
only.
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.
You'll find yourself needing to add temporary data to an individual user. Sessions allow you to do this by adding a key value pair and attaching it to the user's IP address. You can reset the session data anytime you want; which is usually done on logout or login actions.
The Session features are added to the framework through the SessionProvider
Service Provider. This provider needs to be between the AppProvider
and the RouteProvider
. For Masonite 1.5+, this provider is already available for you.
It's important to note that the Session will default to the memory
driver. This means that all session data is stored in an instantiated object when the server starts and is destroyed when the server stops. This is not good when using a WSGI server like Gunicorn which might use multiple workers because each worker will create it's own memory state and requests may jump from worker to worker unpredictably. If you are only using 1 worker then this won't be an issue as long as the worker doesn't die and reset for the duration of the server's life. In this case you should use another driver that doesn't have the memory issue like the cookie
driver which will store all session information as cookies instead of in memory.
There are a two ideas behind sessions. There is session data and flash data. Session data is any data that is persistent for the duration of the session and flash data is data that is only persisted on the next request. Flash data is useful for showing things like success or warning messages after a redirection.
Session data is automatically encrypted and decrypted using your secret key when using the cookie
driver.
Sessions are loaded into the container with the Session
key. So you may access the Session
class in any part of code that is resolved by the container. These include controllers, drivers, middleware etc:
Data can easily be persisted to the current user by using the set
method. If you are using the memory
driver, it will connect the current user's IP address to the data. If you are using the cookie
then it will simply set a cookie with a s_
prefix to notify that it is a session and allows better handling of session cookies compared to other cookies.
This will update a dictionary that is linked to the current user.
You can also set a dictionary as a session value and it will automatically JSON encode and decode as you set and get the key:
When you get the key from the session it will turn it back into a dictionary.
You can use it on flash as well:
Data can be pulled from the session:
Very often, you will want to see if a value exists in the session:
You can get all data for the current user:
Data can be inserted only for the next request. This is useful when using redirection and displaying a success message.
When using the cookie
driver, the cookie will automatically be deleted after 2 seconds of being set.
You can of course change the drivers on the fly by using the SessionManager
key from the container:
It's important to note that changing the drivers will not change the session()
function inside your templates (more about templates below).
The SessionProvider
comes with a helper method that is automatically injected into all templates. You can use the session helper just like you would use the Session
class.
This helper method will only point to the default driver set inside config/session.py
file. It will not point to the correct driver when it is changed using the SessionManager
class. If you need to use other drivers, consider passing in a new function method through your view or changing the driver value inside config/session.py
You could use this to create a simple Jinja include template that can show success, warning or error messages. Your template could be located inside a resources/templates/helpers/messages.html
:
Then inside your working template you could add:
Then inside your controller you could do something like:
or as separate statements
Which will show the correct message and message type.
You can reset both the flash data and the session data through the reset
method.
To reset just the session data:
Or to reset only the flash data:
Remember that Masonite will reset flashed data at the end of a successful 200 OK
request anyway so you will most likely not use the flash_only=True
keyword parameter.
Masonite understands the developer need for building modern web applications so Masonite 1.4+ ships with WebSocket support. With a new Service Provider, configuration file and support for the pusher
and ably
drivers out of the box, we can now have full web socket support quickly and easily.
All broadcasting configuration is located in the config/broadcast.py
file. There are only two options: DRIVER
and DRIVERS
. The DRIVER
should hold the value of the driver you want to use such as pusher
:
and DRIVERS
should hold the configuration data:
Each driver may require it's own individual setting values so be sure to check the documentation for the driver you are using. For the ably
and pusher
drivers, these are the only values you will need.
Make sure that the key in the DRIVER
setting has a corresponding key in the DRIVERS
setting.
If you are using Pusher you will need the Pusher library:
If you are using Ably you will need the ably driver:
Since we have a ServiceProvider
Service Provider which takes care of the container bindings for us, we can now it simply by passing Broadcast
into our parameter list in our controller methods like so:
We can change the driver on the fly as well:
All drivers have the same methods so don't worry about different drivers having different methods.
We can send data through our WebSocket by running:
That's it! we have just sent a message to anyone subscribed to the channel_name
channel.
We can also send a dictionary:
We can also send a message to multiple channels by passing a list:
This will broadcast the message out to both channels. We can pass as many channels into the list as we like.
Masonite also has an optional third parameter which is the event name:
Which will pass the event on to whoever is receiving the WebSocket.
You can also swap drivers on the fly:
or you can explicitly specify the class:
Caching is an important aspect to any project and typically is used to speed up data that never changes and required a lot of resources to get. Powerful caching support is important in any application and Masonite comes with great caching support out of the box.
We need the CacheProvider
in order to activate caching with Masonite. We do so simple by going to our config/application.py
file and adding the Service Provider masonite.providers.CacheProvider.CacheProvider
to the PROVIDERS
list.
All configuration settings are inside the config/cache.py
file. Masonite only comes with a simple disk
driver which stores all of your cache on the file system.
By default, Masonite will store the cache under the bootstrap/cache
directory but can be changed simply inside the DRIVERS
dictionary in the config/cache.py
file. For example to change from bootstrap/cache
to the storage/cache/templates
directory, this will look like:
Masonite also supports a Redis driver. Make sure the Redis server is running and:
Add the required environment keys to your .env
file and you are good to go!
To start using the cache, we can use the Cache
alias that is loaded into the container from the CacheProvider
Service Provider. We can retrieve this from the container inside any method that is resolved by the container such as drivers, middleware and controllers. For example we can retrieve it from our controller method like so:
We can easily store items into the cache by doing:
This will create a bootstrap/cache/key.txt
file which contains a simple value
.
Also note that the directory will be automatically created if it does not exist.
We may only want to cache something for a few seconds or a few days so we can do something like:
This will store the cache for 5 seconds. If you try to retrieve this value after 5 seconds, the Cache class will return None
so be sure to check.
It wouldn't be very good if we could only store values and not retrieve them. So we can also do this simple by doing:
Again this will return None
if a cache is expired. If there is no time limit on the cache, this will simply always return the cache value.
You can also explicitly check if a cache is still valid by doing:
This will return a boolean if a key is valid. This means it is not expired.
We'll have to sometimes check if a cache even exists so we can do that by running:
Which will return a boolean if the cache exists or not.
We may also want to update a cache. For a real world example, this is used for API's for example when updating the cache for rate limiting. This will not reset the expiration, only update the value.
You can delete a cache by key using:
There are a lot of times when you need to validate incoming input either from a form or from an incoming json request. It is wise to have some form of backend validation as it will allow you to build more secure applications. Masonite provides an extremely flexible and fluent way to validate this data.
Validations are based on rules where you can pass in a key or a list of keys to the rule. The validation will then use all the rules and apply them to the dictionary to validate.
You can see a .
Incoming form or JSON data can be validated very simply. All you need to do is import the Validator
class, resolve it, and use the necessary rule methods.
This whole snippet will look like this in your controller method:
This validating will read like "user and email are required and the terms must be accepted" (more on available rules and what they mean in a bit)
Note you can either pass in a single value or a list of values
Sometimes you may cross a time where you need to create a new rule that isn't available in Masonite or there is such a niche use case that you need to build a rule for.
In this case you can create a new rule.
You can easily create a new rule boiler plate by running:
There is no particular reason that rules are lowercase class names. The main reason it is improves readability when you end up using it as a method if you choose to register the rule with the validation class like you will see below.
This will create a boiler plate rule inside app/rules/equals_masonite.py that looks like:
Our rule class needs 3 methods that you see when you run the rule command, a passes
, message
and negated_message
methods.
Passes Method
The passes method needs to return some kind of boolean value for the use case in which this rule passes.
For example if you need to make a rule that a value always equals Masonite then you can make the method look like this:
When validating a dictionary like this:
then
the attribute
will be the value (Masonite
)
the key
will be the dictionary key (name
)
the dictionary
will be the full dictionary in case you need to do any additional checks.
Message method
The message method needs to return a string used as the error message. If you are making the rule above then our rule may so far look something like:
Negated Message
The negated message method needs to return a message when this rule is negated. This will basically be a negated statement of the message
method:
Now the rule is created we can use it in 1 of 2 ways.
Importing our rule
We can either import directly into our controller method:
or we can register our rule and use it with the Validator class as normal.
Register the rule
In any service provider's boot method (preferably a provider where wsgi=False
to prevent it from running on every request) we can register our rule with the validator class.
If you don't have a provider yet we can make one specifically for adding custom rules:
Then inside this rule provider's boot method we can resolve and register our rule. This will look like:
Now instead of importing the rule we can just use it as normal:
notice we called the method as if it was apart of the validator class this whole time.
Registering rules is especially useful when creating packages for Masonite that contain new rules.
In addition to validating the request class we can also use the validator class directly. This is useful if you need to validate your own dictionary:
Just put the dictionary as the first argument and then each rule being its own argument.
Masonite validation has a convenient decorator you can use on your controller methods. This will prevent the controller method being hit all together if validation isn't correct:
This will return a JSON response. You can also choose where to redirect back to:
As well as redirect back to where you came from (if you use the {{ back() }}
template helper)
Both of these redirections will redirect with errors and input. So you can use the
{{ old() }}
template helper to get previous input.
Rule enclosures are self contained classes with rules. You can use these to help reuse your validation logic. For example if you see you are using the same rules often you can use an enclosure to always keep them together and reuse them throughout your code base.
You can create a rule enclosure by running:
You will then see a file generated like this inside app/rules:
You can then fill the list with rules:
You can then use the rule enclosure like this:
You can also use this in addition to other rules:
Working with errors may be a lot especially if you have a lot of errors which results in quite a big dictionary to work with.
Because of this, Masonite Validation comes with a MessageBag
class which you can use to wrap your errors in. This will look like this:
You can easily get all errors using the all()
method:
This is just the opposite of the any()
method.
You can also merge an existing dictionary into the bag with the errors:
Sometimes you will need to check values that aren't on the top level of a dictionary like the examples shown here. In this case we can use dot notation to validate deeper dictionaries:
notice the dot notation here. Each .
being a deeper level to the dictionary.
Sometimes your validations will have lists and you will need to ensure that each element in the list validates. For example you want to make sure that a user passes in a list of names and ID's.
For this you can use the * asterisk to validate these:
Here is an example to make sure that street is a required field:
All errors returned will be very generic. Most times you will need to specify some custom error that is more tailored to your user base.
Each rule has a messages keyword arg that can be used to specify your custom errors.
Now instead of returning the generic errors, the error message returned will be the one you supplied.
Leaving out a message will result in the generic one still being returned for that value.
By default, Masonite will not throw exceptions when it encounters failed validations. You can force Masonite to raise a ValueError
when it hits a failed validation:
Now if the required rule fails it will throw a ValueError
. You can catch the message like so:
You can also specify which exceptions should be thrown with which key being checked by using a dictionary:
All other rules within an explicit exception error will throw the ValueError
.
In addition to using the methods provided below, you can also use each one as a pipe delimitted string. For example these two validations are identical:
These rules are identical so use whichever feels more comfortable.
The accepted rule is most useful when seeing if a checkbox has been checked. When a checkbox is submitted it usually has the value of on
so this rule will check to make sure the value is either on, 1, or yes.
This is used to verify that the domain being passed in is a DNS resolvable domain name. You can also do this for email addresses as well. The preferred search is domain.com but Masonite will strip out http://
, https://
and www
automatically for you.
Used to make sure the date is a date after today. In this example, this will work for any day that is 2019-10-21 or later.
You may also pass in a timezone for this rule:
Used to make sure the date is a date before today. In this example, this will work for any day that is 2019-10-19 or earlier.
You may also pass in a timezone for this rule:
This is used to make sure a value exists inside an iterable (like a list or string). You may want to check if the string contains the value Masonite for example:
This rule is used to make sure a key is "confirmed". This is simply a key_confirmation
representation of the key.
For example, if you need to confirm a password
you would set the password confirmation to password_confirmation
.
Used for running a set of rules when a set of rules does not match. Has a then()
method as well. Can be seen as the opposite of when.
Used to make sure a dictionary value is equal to a specific value
This is useful for verifying that a value is a valid email address
Checks to see if a key exists in the dictionary.
This is good when used with the when rule:
This is used to make sure a value is greater than a specific value
Used when you need to check if an integer is within a given range of numbers
You can also check if the input is a valid IPv4 address:
Used to make sure if a value is in a specific value
notice how 5 is in the list
This will negate all rules. So if you need to get the opposite of any of these rules you will add them as rules inside this rule.
For example to get the opposite if is_in
you will do:
This will produce an error because age it is looking to make sure age is not in the list now.
Checks to see the date and time passed is in the future. This will pass even if the datetime is 5 minutes in the future.
You may also pass in a timezone for this rule:
Checks to see the date and time passed is in the past. This will pass even if the datetime is 5 minutes in the past.
You may also pass in a timezone for this rule:
Used to make sure a given value is actually a JSON object
Used to make sure a string is of a certain length
This is used to make sure a value is less than a specific value
Used to make sure the value is None
Used to make sure a value is a numeric value
Sometimes you will want only one of several fields to be required. At least one of them need to be required.
This will pass because at least 1 value has been found: user
.
You can also use the phone validator to validate the most common phone number formats:
The available patterns are:
123-456-7890
(123)456-7890
Used to make sure the value is actually available in the dictionary. This will add errors if the key is not present
Used to make sure the value is a string
The strong rule is used to make sure a string has a certain amount of characters required to be considered a "strong" string.
This is really useful for passwords when you want to make sure a password has at least 8 characters, have at least 2 uppercase letters and at least 2 special characters.
You can also validate that a value passed in a valid timezone
Used to make sure a value is a truthy value. This is anything that would pass in a simple if statement.
Conditional rules. This is used when you want to run a specific set of rules only if a first set of rules succeeds.
For example if you want to make terms be accepted ONLY if the user is under 18
Framework hooks are essentially events that are emitted that you are able to "hook" into. If you want to add support for , which is an exception and error tracking solution, then you want to tie into the Exception Hook. When an exception is encountered it will look for your class in the container and execute it.
Currently there is only the Exception Hook that you can tie into but there will be other hooks in later releases of Masonite such as View Hooks and Mail Hooks.
For the purposes of learning about Framework Hooks, we will walk through how to implement into a Masonite application by adding it to the Exception Hook.
The Exception Hook is fired when an exception is thrown in an application. Anytime you would normally see the debug view when developing is when this hook will fire. This hook may not be fired if the server does not boot up because of an exception depending on how far into the container the exception is thrown but any normal application exceptions encountered will fire this hook.
The exception hook to tie into is ExceptionHook. This means that the key in the container should end with ExceptionHook and Masonite will call it when the Exception Hook is thrown. We can load things into the container such as:
Notice here that our key ends with ExceptionHook and that we instantiated the object. Let's explore creating this entirly from scratch.
Let's explore how we can simply add to our application. This should take about 5 minutes.
Let's create a class called SentryHook
and put it into app/hooks/sentry.py
.
This should be the basic structure for a hook. All hooks require a load method. This load method will always be passed the application container so it always requires that parameter. From here we can do whatever we need to by making objects from the container.
But for this example we actually don't need the container so we can ignore it.
This should be the finished hook:
Now let's walk through how we can simply tie this into the container so it will be called when an exception is thrown in our project.
Remeber that all we need to do is call is add it to the container and append the correct string to the key.
We can create a new Service Provider to store our hooks so let's make one.
This will create a new Service Provider inside app/providers/SentryServiceProvider.py
that looks like:
Now let's just add our hook to it:
Notice that the key we binded our object to ends with "ExceptionHook." What we put before this part of the string is whatever you want to put. Also note that we also instantiated our SentryHook()
and didn't put SentryHook
And finally add the Service Provider to our PROVIDERS
constant in our config/providers.py
file:
You can build handlers to handle specific exceptions that are thrown by your application. For example if a TemplateNotFound exception is thrown then you can build a special exception handler to catch that and return a special view or a special debug screen.
Exception handlers are simple classes that have a handle method which accepts the exception thrown:
The constructor of all exception handlers are resolved by the container so you can hint any dependencies you need. Once the constructor is resolved then the handle method will be called.
Once the handle method is called then Masonite will continue with the rest of the WSGI logic which is just setting the status code, headers and returning a response.
Now that we have our exception handler we will need to register it in the container using a special naming convention. The naming convention is: ExceptionNameOfErrorHandler
. The name of the exception that we want to be catching is called TemplateNotFound so we will need to bind this into the container like so:
While an exception handler will actually handle the incoming exception, exception listeners are a little different.
Masonite can have several listeners registered with the framework that will listen to specific (or all) exceptions and have that exception passed into it if one is raised. It will then perform any logic it needs until an exception handler finally handles the exception.
A listener is a simple class that requires a list of exceptions to listen for. Here is a simple boilerplate of an exception listener:
Note that the __init__
method of a listener is resolved by the container so feel free to type hint anything you need there.
Finally it requires a handle
method which takes 3 arguments. the exception
that was thrown, the file
that the exception was thrown in and the line
the exception was thrown on.
You can either listen to any number of exceptions or all exceptions by passing in a *
to the listens
attribute:
A good use case for this would be Masonite Logging package which uses this to log any exceptions.
You can register an exception listener directly to the container with any service provider. For easy use, you can use a simple
bind to the container which will bind the class to the container with the name of the class as the key:
Your listener will now run whenever an exception occurs that your listener is listening to.
Sometimes your templates will not change that often and may have a lot of logic that takes times to run such as several loops or even a very large order of magnitude. If this page is being hit several times per day or even several times per second, you can use template caching in order to put less strain on your server.
This is a powerful feature that will reduce the load of your server for those resource hungry and complex templates.
This feature is introduced in Masonite 1.4 and above. You can check your Masonite version by running pip show masonite
which will give you the details of the masonite package you have installed.
You can use whatever cache you like for caching templates. Only the disk
driver is supported out of the box but you can create any drivers you like. Read the documentation on how to create drivers. If you do create a driver, consider making it available on PyPi so others can install it into their projects. If you'd like to contribute to the project and add to the drivers that come freshly installed with Masonite then please visit Masonite's GitHub repository and open an issue for discussing.
All caching configuration is inside config/cache.py
which contains two settings, DRIVER
and DRIVERS
.
We can set the driver we want to use by specifying like so:
Like every other configuration, you'll need to specify the options inside the DRIVERS
dictionary:
All templates cached will be inside that folder. You may wish to specify another directory for your templates such as:
In order to cache templates for any given amount of time, you can attach the .cache_for()
method onto your view. This looks like:
This will cache the template for 5 seconds. After 5 seconds, the next hit on that page will show the non cached template and then recache for another 5 seconds.
What has always been annoying in many libraries and frameworks is the distinguishhment between plural and singular such as second
and seconds
. So if we only want to do 1 minute then that would look like:
There are several cache lengths we can use. Below is all the options for caching:
We can cache for several seconds:
or several minutes:
or several hours:
or several days:
or several months:
or even several years:
Read more about this in the documentation.
It's great (and convenient) to add craft commands to a project so developers can use your package more efficiently. You can head over to to learn how to create a command. It only involves a normal command class and a Service Provider.
Make sure this command is added to your Service Provider and the developer using your package adds it to the PROVIDERS
list as per the documentation.
There are several different ways for redirecting like redirecting to a named route or redirecting back to the previous route. For a full list of request redirection methods read the docs.
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
.
Remember that Masonite uses a Service Container and automatic dependency injection. You can read more about it under the documentation.
Ok great so now let's add sentry to our application. You can sign up with which will show you the basic 3 lines you need to add Sentry to any Python project.
That's it. The key in the client should be available through the dashboard.
That's it! Now everytime an exception is thrown, this will run our SentryHook class that we binded into the container and the exception should pop up in our dashboard.
Masonite comes with email support out of the box. Most projects you make will need to send emails upon actions like account creation or notifications. Because email is used so often with software applications, masonite provides mail support with several drivers.
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.
You may need to use an ssl version of SMTP depending on the service you are using. You can specify to use SSL by setting that option in your smtp driver configuration in config/mail.py
:
Thats it! As long as the authentication works, we can send emails.
Remember that it is safe to put sensitive data in your .env
file because it is not committed to source control and it is inside the .gitignore
file by default.
Mailgun does not use SMTP and instead uses API calls to their service to send emails. Mailgun only requires 2 configuration settings:
If you change to using Mailgun then you will need to change the driver. By default the driver looks like:
This means you can specify the mail driver in the .env file:
or we can specify the driver directly inside config/mail.py
Masonite will retrieve the configuration settings for the mailgun driver from the DRIVERS
configuration setting which Masonite has by default, you do not have to change this.
The Terminal driver simply prints out your email message in the terminal. Makes testing and development super easy. To use the terminal driver you'll need to enter a few configuration settings.
The Log driver simply prints out your email message into a log file. To use the log driver you'll need to enter a few configuration settings.
Masonite will retrieve the configuration settings for the log driver from the DRIVERS
configuration setting which Masonite has by default, you do not have to change this.
The Mail
class is loaded into the container via the the MailProvider
Service Provider. We can fetch this Mail
class via our controller methods:
We can send an email like so:
You can also obviously specify a specific user:
you can easily queue the sending of emails by using the queue method before the send method like so:
All mail drivers are managed by the MailManager
class and bootstrapped with the MailProvider
Service Provider.
We can specify which driver we want to use. Although Masonite will use the DRIVER
variable in our mail
config file by default, we can change the driver on the fly.
You can see in our MailProvider
Service Provider that we can use the MailManager
class to set the driver. We can use this same class to change the driver:
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 also specify the subject:
You can specify which address you want the email to appear from:
The most common place to put your email templates is inside resources/templates/mail
.
If you don't want to pass a string as the message, you can pass a view template.
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
You are also able to pass data into our mail templates. This data is passed in as a dictionary that contains a key which is the variable with the corresponding value. We can pass data to the function like so:
Masonite Events is a simple to use integration for subscribing to events. These events can be placed throughout your application and triggered when various actions occur. For example you may want to do several actions when a user signs up like:
Send an email
Subscribe to MailChimp
Update a section of your database
These actions can all be executed with a single line of code in you controller once you have setup your listeners
First we'll need to install the package using pip:
Once installed we'll need to add the provider to our providers list:
This Service Provider will add a new event:listener
command we can use to create new events. We'll walk through step by step on how to create one below.
Masonite Events allows you to subscribe to arbitrary event actions with various event listeners. In this article we will walk through the steps on setting up listeners for a new user subscribing to our application.
We can create our event listener by executing the following command:
This will create a new event in the app/events directory that looks like this:
This is a very simple class. We have the __init__
method which is resolved by the container and we have a handle
method, also resolved by the container.
This means that we can use syntax like:
The subscribe attribute can be used as a shorthand later which we will see but it should be a list of events we want to subscribe to:
We can listen to events simply by passing the listener into one of your applications Service Provider's boot methods. Preferably this Service Provider should have wsgi=False
so that you are not continuously subscribing the same listener on every request.
You likely have a Service Provider whose wsgi attribute is false but let's create a new one:
Make sure we set the wsgi attribute to False:
Now we can import our listener and add it to our boot method:
This is the recommended approach over the more manual approach found below but both options are available if you find a need for one over the other.
Since we have a subscribe attribute on our listener, we can simply pass the action into the subscribe method. This will subscribe our listener to the SomeAction
and the user.subscribed
action that we specified in the subscribe
attribute of our listener class.
If we don't specify the actions in the subscribe attribute, we can manually subscribe them using the listen method in our Service Provider's boot method:
Ensure that the second parameter in the listen method is a list; even if it has only a single value
Now that we have events that are being listened to, we can start firing events. There are two ways to fire events. We can do both in any part of our application but we will go over how to do so in a controller method.
Masonite Events also comes with a new builtin helper method:
Both of these methods will fire all our listeners that are listening to the user.subscribed
event action.
As noted briefly above, we can subscribe to classes as events:
This will go through the same steps as an event subscribed with a string above.
We can also fire events using an * wildcard action:
This will fire events such as user.subscribed
, user.created
, user.deleted
.
We can also fire events with an asterisk in the front:
This will fire events such as user.created
, dashboard.created
and manager.created
.
We can also fire events with a wildcard in the middle:
This will fire events such as user.manager.created
, user.employee.created
and user.friend.created
.
Sometimes you will want to pass an argument from your controller (or wherever you are calling your code) to your event's handle
method. In this case you can simply pass keyword arguments to your fire
method like so:
and you can fetch these values in your handle
method using the argument
method:
Drivers are simply extensions to features that are managed by the Manager Pattern. If we have a UploadManager
then we might also create a UploadDiskDriver
and a UploadS3Driver
which will be able to upload to both the file system (disk) and Amazon S3. In the future if we have to upload to Microsoft Azure or Google Cloud Storage then we simply create new drivers like UploadAzureDriver
and UploadGoogleDriver
which are very simple to create. Drivers can be as small as a single method or have dozens of methods. The Manager Pattern makes it dead simple to expand the functionality of a Manager and add capabilities to Masonite's features.
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:
In order to ensure that all drivers follow the same structure, we can use Contracts. Contracts are essentially interfaces if you are coming from other programming languages. They make sure that the classes they inherit into have the minimum methods necessary in order to be accepted as a driver. If it does not meet the minumum methods necessary then you will keep hitting exceptions every time you tried to run your code.
For our specific driver we should inherit from the UploadContract
:
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.
Great. If you're confused about how the dependency injection Service Container works then read the Service Container documentation.
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. Learn how to create managers under the About Managers documentation. Our manager will know of all drivers that are inside the Service Container. We can create a new service provider which we can use to register classes into our container. Here is an example of what the UploadProvider
will look like:
Notice how we set our storage configuration in the container, binded our drivers and then binded our Manager. Again, our manager will be able to find all our UploadXDrivers
that are loaded into the container. So if we set the DRIVER
inside our configuration file to google
, our manager will look for a UploadGoogleDriver
inside our container. Read more about Managers in the About Managers documentation.
That's it! Drivers are extremely simple and most drivers you create will be a simple class with a single method or two.
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.
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 .composer()
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()
Jinja2 allows adding filters to your views. Before we explain how to add filters to all of your templates, let's explain exactly what a view filter is.
Filters can be attached to view variables in order to change and modify them. For example you may have a variable that you want to turn into a slug and have something like:
In Python, this slug filter is simply a function that takes the variable as an argument and would look like a simple function like this:
That's it! It's important to note that the variable it is filtering is always passed as the first argument and all other parameters are passed in after so we could do something like:
and then our function would look like:
We can add filters simply using the filter
method on the ViewClass
class. This will look something like:
Make sure that you add filters in a Service Provider that has wsgi=False
set. This prevents filters from being added on every single request which is not needed.
That's it! Adding filters is that easy!
View tests are simply custom boolean expressions that can be used in your templates. We may want to run boolean tests on specific objects to assert that they pass a test. For example we may want to test if a user is an owner of a company like this:
In order to do this we need to add a test on the View
class. We can do this in a Service Provider. The Service Provider you choose should preferably have a wsgi=False
attribute so the test isn't added on every single request which could potentially slow down the application.
The code is simple and looks something like this:
That's it! Now we can use the a_company_owner
in our templates just like the first code snippet above!
Notice that we only supplied the function and we did not instantiate anything. The function or object we supply needs to have 1 parameter which is the object or string we are testing.
Jinja2 has the concept of extensions and you can easily add them to your project in a similar way as previous implementations above which is in a Service Provider:
This will add the extension to the view class.
Remember to place this in a service provider where wsgi=False
as this will prevent the extension being added on every request.
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 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 Company
model for example, feel free to do in this configuration file.
This would look something like:
The cookie driver will set a token as a cookie and then fetch the user from the database on every request. For most applications this is fine although you are making an additional query per request just to fetch the user.
This is the most basic authentication driver.
The JWT driver will store an encrypted JWT token inside a cookie with all the authenticated user information. Then when the authenticated user goes to the page, the JWT token is decrypted and fills in the data on the user model without calling the database.
You can set this driver in your .env
file:
There are also 2 options you can set as well. The first option is how long until the jwt token expires. By default this is 5 minutes but you can extend it out longer:
The second option is whether or not the user should reauthenticate with the database after their token has expired. If set to False
, the token will simply continue to refill the user model and set a new token all without touching the database.
Again the default authentication model is the app/User
model which out of the box comes with a __auth__
class attribute. This attribute should be set to the column that you want to authenticate with when a user logs in.
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:
Sometimes your application will be able to either login by email OR by username. You can do this by specifying the __auth__
attribute as a list of columns:
By default, Masonite will use the password
column to authenticate as the password. Some applications may have this changed. Your specific application may be authenticating with a token
column for example.
You can change the password column by speciyfing the __password__
attribute to the column name:
If you want to authenticate a model, you can use the Auth
class that ships with Masonite. This is simply a class that is used to authenticate models with a .login()
method.
In order to authenticate a model this will look like:
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.
You may change the column to be authenticated by simply changing the column value of the __auth__
class attribute. This will look something like:
This will look inside the email
column now and check that column and password. The authentication column is email
by default.
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 LoadUserMiddleware
middleware that will load the user into the request if they are authenticated. Masonite uses the token
cookie in order to retrieve the user using the remember_token
column in the table.
Using this LoadUserMiddleware
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.user()
will return False
if the user is not authenticated. This will look like:
You can easily log users into your application using the Auth class:
Note that the username you supply needs to be in whatever format the __auth__
attribute is on your model. If the email address is the "username", then the user will need to supply their email address.
If you need more direct control internally, you can login by the models ID:
You are now logged in as the user with the ID of 1.
If you only want to login "once", maybe for just authenticating an action or verifying the user can supply the correct credentials, you can login without saving any cookies to the browser:
You can do the same for the normal login method as well:
You can also easily register a user using the register()
method on the Auth
class:
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:
If you wish to require a user to verify their email address and automatically send them an email, you can extend the User
model.
When a user registers this will automatically send them an email asking them to confirm their email address.
You can use the VerifyEmailMiddleware
class to redirect an unverified user.
You can use this middleware in your routes file like so:
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.
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.
You can also explicitly declare the driver as a class:
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 name. We could use that to input into our database if we want. All file uploads will convert the filename into a random 25 character string.
Lastly, we may can specify a filename directly using the filename
keyword argument:
By default, Masonite only allows uploads to accept images for security reasons but you can specify any file type you want to accept by specifying the filetype in the accept
method before calling the store
method.
You can also just accept all file types as well:
You can upload files directly by passing in a open()
file:
This will upload a file directly from the file system to wherever it needs to upload to.
You can also specify the location you want to upload to. This will default to location specified in the config file but we can change it on the fly:
You can use dot notation to search your driver locations. Take this configuration for example:
and you can use dot notation:
Before you get started with uploading to Amazon S3, you will need the boto3 library:
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.
You can also swap drivers on the fly:
or you can explicitly specify the class:
Almost all applications can make use of queues. Queues are a great way to make time intensive tasks seem immediate by sending the task into the background or into a message queue. It's great to send anything and everything into the queue that doesn't require an immediate return value (such as sending an email or firing an API call). The queue system is loaded into masonite via the QueueProvider
Service Provider.
Masonite uses pickle to serialize and deserialize Python objects when appropriate. Ensure that the objects you are serializing is free of any end user supplied code that could potentially serialize into a Python object during the deserialization portion.
It would be wise to read about pickle exploitations and ensure your specific application is protected against any avenues of attack.
All configuration settings by default are in the config/queue.py
file. Out of the box, Masonite supports 3 drivers:
async
amqp
database
The async
driver simply sends jobs into the background using multithreading. The amqp
driver is used for any AMQP compatible message queues like RabbitMQ. If you do create a driver, consider making it available on PyPi so others can also install it. The database
driver has a few additional features that the other drivers do not have if you need more fine-grained control
Jobs 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:
We can run jobs by using the Queue
class. Let's run this job from a controller method:
That's it. This job will now send to the queue and run the handle
method.
Notice in the show method above that we passed in just the class object. We did not instantiate the class. In this case, Masonite will resolve the job constructor. All job constructors are able to be resolved by the container so we can simply pass anything we need as normal:
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 Service Container documentation.
We can also instantiate the job as well if we need to pass in data from a controller method. This will not resolve the job's constructor at all:
The constructor of our job class now will look like:
Whenever jobs are executed, it simply executes the handle method. Because of this we can send our welcome email:
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:
Most of the time you will want to resolve the constructor but pass in variables into the handle()
method. This can be done by passing in an iterator into the args=
keyword argument:
This will pass to your handle method:
You can also call any arbitrary function or method using the queue driver. All you need to do is pass the reference for it in the push method and pass any arguments you need in the args parameter like so:
This will then queue this function to be called later.
Note that you will not be able to get a response value back. Once it gets sent to the queue it will run at an arbitrary time later.
The async
queue driver will allow you to send jobs into the background to run asynchronously. This does not need any third party services like the amqp
driver below.
The async driver has 2 different modes: threading
and multiprocess
. The differences between the two is that threading
uses several threads and multiprocess
uses several processes. Which mode you should use depends on the type of jobs you are processing. You should research what is best depending on your use cases.
You can change the mode inside the config/queue.py
file:
During development it may be hard to debug asyncronous tasks. If an exception is thrown it will be hard to catch that. It may appear that a job is never ran.
In order to combat this you can set the blocking
setting in your config/queue.py
file:
Blocking bascially makes asyncronous tasks run syncronously. This will enable some reporting inside your terminal that looks something like:
This will also run tasks syncronously so you can find exceptions and issues in your jobs during development.
For production this should be set to False
.
It may be good to set this setting equal to whatever your APP_DEBUG
environment variable is:
This way it will always be blocking during development and automatically switch to unblocking during production.
The amqp
driver can be used to communicate with RabbitMQ services.
In order to get started with this driver you will need to install RabbitMQ on your development machine (or production machine depending on where you are running Masonite)
You can find the installation guide for RabbitMQ here.
Once you have RabbitMQ installed you can go ahead and run it. This looking something like this in the terminal if ran successfully:
Great! Now that RabbitMQ is up and running we can look at the Masonite part.
Now we will need to make sure our driver and driver configurations are specified correctly. Below are the default values which should connect to your current RabbitMQ configuration. This will be in your app/queue.py
file
If your rabbit MQ instance requires a vhost
but doesn't have a port, we can add a vhost
and set the port to none. vhost
and port
both have the option of being None
. If you are developing locally then vhost
should likely be left out all together. The setting below will most likely be used for your production settings:
The database driver will store all jobs in a database table called queue_jobs
and on fail, will store all failed jobs in a failed_jobs
table if one exists. If the failed_jobs
table does not exist then it will not store any failed jobs and any jobs that fail will be lost.
In order to get these two queue table you can run the queue:table
command with the flag on which table you would like:
This command will create the queue_jobs
migration where you can store your jobs:
This command will create the failed_jobs
migration where you can store your failed jobs:
Once these migrations are created you can run the migrate command:
Jobs can be easily delayed using the database
driver. Other drivers currently do not have this ability. In order to delay a job you can use a string time using the wait
keyword.
We can now start the worker using the queue:work
command. It might be a good idea to run this command in a new terminal window since it will stay running until we close it.
This will startup the worker and start listening for jobs to come in via your Masonite project.
You can also specify the driver you want to create the worker for by using the -d
or --driver
option
You may also specify the channel
as well. channel
may mean different things to different drivers. For the amqp
driver, the channel
is which queue to listen to. For the database
driver, the channel
is the connection to find the queue_jobs
and queue_failed
tables.
That's it! send jobs like you normally would and it will process via RabbitMQ:
you can also specify the channel to push to by running:
Sometimes your jobs will fail. This could be for many reasons such as an exception but Masonite will try to run the job 3 times in a row, waiting 1 second between jobs before finally calling the job failed.
If the object being passed into the queue is not a job (or a class that implements Queueable
) then the job will not requeue. It will only ever attempt to run once.
Each job can have a failed
method which will be called when the job fails. You can do things like fix a parameter and requeue something, call other queues, send an email to your development team etc.
This will look something like:
It's important to note that only classes that extend from the Queueable
class will handle being failed. All other queued objects will simply die with no failed callback.
Notice that the failed method MUST take 2 parameters.
The first parameter is the payload which tried running which is a dictionary of information that looks like this:
and the error may be something like division by zero
.
By default, when a job is failed it disappears and cannot be ran again since Masonite does not store this information.
If you wish to store failed jobs in order to run them again at a later date then you will need to create a queue table. Masonite makes this very easy.
First you will need to run:
Which will create a new migration inside databases/migrations
. Then you can will migrate it:
Now whenever a failed job occurs it will store the information inside this new table.
You can run all the failed jobs by running
This will get all the jobs from the database and send them back into the queue. If they fail again then they will be added back into this database table.
You can modify the settings above by specifying it directly on the job. For example you may want to specify that the job reruns 5 times instead of 3 times when it fails or that it should not rerun at all.
Specifying this on a job may look something like:
This will not try to rerun when the job fails.
You can specify how many times the job will rerun when it fails by specifying the run_times
attribute:
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.
Make sure you put all your database your credentials and driver into the .env
file. This file is in the .gitignore
file for you so make sure that this file never gets submitted to source control.
If you are using MySQL you can install:
or:
Postgres databases only have a single option for a package:
If you are using SQLite configure your .env
file with the following options
Your sqlite database will be created on project root directory.
Before you start using the ORM you will receive an exception saying that you need to install one of the required packages for your database driver.
Because models and migrations are separated, we never have to touch our model in order to make alterations to our database tables. In order to make a migration we can run a craft command:
This command will create a migration for an existing table. A migration on an existing table will migrate into the database in a certain way so it's important to specify the --table
flag in the command.
In order to create a migration file for a new table that doesn't yet exist (but will after the migration) you can instead use the --create
flag like so:
This will create a migration that will create a table, as well as migrate the columns you specify.
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 Orators Schema Builder 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.
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 Orator Schema Columns you can use. We'll mention the foreign key here though.
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.
Check the Orator documentation for more information on creating a migration file.
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:
Masonite takes security seriously and wants full transparency with security vulnerabilities so we document each security release in detail and how to patch or fix it.
There were 2 issues involved with this release. The first is that input data was not being properly sanitized so there was an XSS vulnerability if the developer returned this directly back from a controller.
Another issue was that there were no filters on what could be uploaded with the upload feature. The disk driver would allow any file types to be uploaded to include exe, jar files, app files etc.
There was no reported exploitation of this. These were both proactively caught through analyzing code and possible vulnerabilities.
The fix for the input issue was simply to just escape the input before it is stored in the request dictionary. We used the html core module that ships with Python and created a helper function to be used elsewhere.
The fix to the second issue of file types was to limit all uploads to images unless explicitly stated otherwise through the use of the accept
method.
The patch for this is to simply upgrade to 2.1.3 and explicitly state which file types you need to upload if they are not images like so:
Those choices to accept those files should be limited and up to the developer building the application sparingly.
Masonite comes with bcrypt out of the box but leaves it up to the developer to actually encrypt things like passwords. You can opt to use any other hashing library but bcrypt is the standard of a lot of libraries and comes with some one way hashing algorithms with no known vulnerabilities. Many of hashing algorithms like SHA-1 and MD5 are not secure and you should not use them in your application.
You can read the bcrypt documentation here.
Also, we make sure that Javascript cannot read your cookies. It's important to know that although your website may be secure, you are susceptible to attacks if you import third party Javascript packages (since those libraries could be compromised) which can read all cookies on your website and send them to the hacker.
Other frameworks use cryptographic signing which attaches a special key to your cookies that prevents manipulation. This does't make sense as a major part of XSS protection is preventing third parties from reading cookies. It doesn't make sense to attach a digital signature to a plaintext cookie if you don't want third parties to see the cookie (such as a session id). Masonite takes this one step further and encrypts the entire string and can only be decrypted using your secret key (so make sure you keep it secret!).
In your .env
file, you will find a setting called KEY=your-secret-key
. This is the SALT that is used to encrypt and decrypt your cookies. It is important to change this key sometime before development. You can generate new secret keys by running:
This will generate a new key in your terminal which you can copy and paste into your .env
file. Your config/application.py
file uses this environment variable to set the KEY
configuration setting.
Additionally you can pass the --store
flag which will automatically set the KEY=
value in your .env
file for you:
Remember to not share this secret key as a loss of this key could lead to someone being able to decrypt any cookies set by your application. If you find that your secret key is compromised, just generate a new key.
You can use the same cryptographic signing that Masonite uses to encrypt cookies on any data you want. Just import the masonite.sign.Sign
class. A complete signing will look something like:
By default, Sign()
uses the encryption key in your config/application.py
file but you could also pass in your own key.
This feature uses pyca/cryptography for this kind of encryption. Because of this, we can generate keys using Fernet.
Just remember to store the key you generated or you will not be able to decrypt any values that you encrypted in the first place.
Bcrypt is very easy to use and basically consists of a 1 way hash, and then a check to verify if that 1 way hash matches an input given to it.
It's important to note that any values passed to bcrypt need to be in bytes.
Again, all values passed into bcrypt need to be in bytes so we can pass in a password using the password helper function:
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.
Masonite allows you to easily add security headers to your application. Masonite adds some sensible defaults but you can modify them as you need.
All you need to do is add the middleware to your HTTP_MIDDLEWARE
constant in your config/middleware.py
file:
This will add these default headers for your server:
If you want to change or add any headers, you just need to specify them in your config/middleware.py file and this middleware will automatically pick them up. For example you can change the X-Frame-Options
header like this:
This will then change your headers to:
Notice the change in the new header we changed.
You may also choose to use CORS for your application for advanced security measures. Using CORS is very similar to the secure headers above.
This middleware needs to be at the TOP of the HTTP_MIDDLEWARE
stack so the request will not be rejected inside the other middleware.
To get started just import the CorsProvider
class into your config/providers.py
file and add it to your PROVIDERS
list:
Then inside your config/middleware.py
file you can put your CORS headers as a dictionary. Here is a list of sensible defaults:
Now if you go to a browser you will see these headers being sent as a response from your server.
CSRF protection typically entails setting a unique token to the user for that page request that matches the same token on the server. This prevents any person from submitting a form without the correct token. There are many online resources that teach what CSRF does and how it works but Masonite makes it really simple to use.
The CSRF features for Masonite are located in the CsrfProvider
Service Provider and the CsrfMiddleware
. If you do not wish to have CSRF protection then you can safely remove both of these.
The CsrfProvider
simply loads the CSRF features into the container and the CsrfMiddleware
is what actually generates the keys and checks if they are valid.
By default, all POST
requests require a CSRF token. We can simply add a CSRF token in our forms by adding the {{ csrf_field }}
tag to our form like so:
This will add a hidden field that looks like:
If this token is changed or manipulated, Masonite will throw an InvalidCsrfToken
exception from inside the middleware.
If you attempt a POST
request without the {{ csrf_field }}
then you will receive a InvalidCsrfException
exception. This just means you are either missing the Jinja2 tag or you are missing that route from the exempt
class attribute in your middleware.
You can get also get the token that is generated. This is useful for JS frontends where you need to pass a CSRF token to the backend for an AJAX call
For ajax calls, the best way to pass CSRF tokens is by setting the token inside a parent template inside a meta
tag like this:
And then you can fetch the token and put it wherever you need:
You can then pass the token via the X-CSRF-TOKEN
header instead of the __token
input for ease of use.
Not all routes may require CSRF protection such as OAuth authentication or various webhooks. In order to exempt routes from protection we can add it to the exempt
class attribute in the middleware located at app/http/middleware/CsrfMiddleware.py
:
Now any POST routes that are to your-domain.com/oauth/github
are not protected by CSRF and no checks will be made against this route. Use this sparingly as CSRF protection is crucial to application security but you may find that not all routes need it.
You can also use *
wildcards for exempting several routes under the same prefix. For example you may find yourself needing to do this:
This can get a bit repetitive so you may specify a wildcard instead:
The Hash ID Masonite essential feature is design for you to easily be able to screen your ID's in your URL's while being able to automatically decode them before they reach your controller method.
For example we can encode a value like 10
and get back l9avmeG
. When we pass this value into our endpoint like /dashboard/user/l9avmeG
, we get back the correct value in our controller:
This is very useful for hiding primary ID's and this package makes it very simple to do so.
This package requires:
Masonite 2.0+
You can install the required dependencies for the Hash ID feature by running:
Once you have it installed you can then add a middleware and a Service Provider
You can add the middleware in your config/middleware.py
file:
You also need to add the Service Provider as well:
Great. Once you do those 2 things you are ready to get started.
In your views you can use the Hash ID template helper which was added when you added the Service Provider. We can use this like so:
This feature is designed to be transparent. When the middleware method runs it will check all the request inputs and try to decode them. It will insert the decoded values back into the request input and ignore anything it could not correctly decode.
You can get the inputs like normal:
This also works for inputs so if you have an endpoint like /dashboard/user?id=l9avmeG
then you can still get the correct value by using input()
.
You can also encode values yourself by importing the essentials helper:
You have a few options for decoding. You can decode a normal string that you previously encoded by passing in the decode
keyword.
You can also pass in a dictionary of values to decode. This helper will try to decode each one and insert the correct value. It will skip the ones it cannot decode.
This is actually what is happening under the hood of the feature.
Masonite comes with several drivers that all work great out of the box and in development. Some drivers are better than others in production and some cannot be used in production because of how servers are setup.
For example, the caching or upload drivers should not store anything on the server itself if you are deploying onto an ephemeral deployment platform like Heroku. In these cases you should use a caching driver like Redis and an upload driver like Amazon S3.
The session driver has two options out of the box: memory
and cookie
. The cookie driver will store session data such as success messages, user data or anything else you store with Session.store().
The memory driver will store all session data in a giant dictionary while the server is running and store all data under the IP address. The data will completely lost when the server stops running. The memory driver is great for development and instances where you need to test features without always deleting cookie data.
You can imagine that if you have the memory
driver set and 10,000 users then thats 10,000 dictionary keys each containing several values of session data.
For production, this setting should be set to cookie
to maximize server performance and server resources.
Often your application will require some kind of recurring task that should happen at a specific time of the week, every minute, or every few months. These tasks can be things like:
Reading XML from a directory and importing it into a remote database
Checking if a customer is still a subscriber in Stripe and updating your local database
Cleaning your database of unneeded data every minute or so
Send an API call to a service in order to fire certain events
Or anything in between. There are lots of use cases for simple tasks to be ran during certain parts of the day or even "offline" hours when your employees are gone.
First we will need to install the scheduler feature. We can simply pip install it:
and then add the Service Provider to our PROVIDERS
list in config/providers.py:
This provider will add several new features to Masonite. The first is that it will add two new commands.
The first command that will be added is the craft schedule:run
command which will run all the schedules tasks (which we will create in a bit).
The second command is a craft task
command which will create a new task under the app/tasks
directory.
Now that we added the Service Provider, we can start creating tasks. Let's create a super basic task that prints "Hi". First let's create the task itself:
This will create a file under app/tasks/SayHi.py
This will be the simple boilerplate for our tasks.
All tasks should inherit the scheduler.task.Task
class. This adds some needed methods to the task itself but also allows a way to fetch all the tasks from the container by collecting them.
Make sure you read about collecting objects from the container by reading the Collecting section of the Service Container documentation.
In order for tasks to be discovered they need to be inside the container. Once inside the container, we collect them, see if they need to run and then decide to execute them or not.
There are two ways to get classes into the container. The first is to bind them into the container manually by creating a Service Provider. You can read the documentation on creating Service Providers if you don't know how to do that.
The other way is to Autoload them. Starting with Masonite 2.0, You can autoload entire directories which will find classes in that directory and load them into the container. This can be done by adding the directory your tasks are located in to the AUTOLOAD config variable inside config/application.py:
This will find all the tasks in the app/tasks directory and load them into the container for you with the key binding being the name of the class.
Now that our task is able to be added to the container automatically, let's start building the class.
Firstly, the constructor of all tasks are resolved by the container. You can fetch anything from the container that doesn't need the WSGI server to be running (which is pretty much everything). So we can fetch things like the Upload, Mail, Broadcast and Request objects. This will look something like:
The handle method is where the logic of the task should live. This is where you should put the logic of the task that should be running recurrently.
We can do something like fire an API call here:
The awesomeness of recurring tasks is telling the task when it should run. There are a few options we can go over here:
A complete task could look something like:
This task will fire that API call every 3 days at 5pm.
All possible options are False
by default. The options here are:
If the time on the task is days
or months
then you can also specify a run_at
attribute which will set the time of day it should should. By default, all tasks will run at midnight if days
is set and midnight on the first of the month when months
is set. We can specify which time of day using the run_at
attribute along side the run_every
attribute. This option will be ignored if run_every
is minutes
or hours
.
You can also set timezones on individual tasks by setting a timezone
attribute on the task:
This feature is designed to run without having to spin up a seperate server command and therefore has some caveats so be sure to read this section to get a full understanding on how this feature works.
Since the scheduler doesn't actually know when the server starts, it doesn't know from what day to start the count down. In order to get around this, Masonite calculates the current day using a modulus operator to see if the modulus of the tasks time and the current time are 0.
For example, if the task above is to be ran (every 3 days) in May then the task will be ran at midnight on May 3rd, May 6th, May 9th, May 12th etc etc. So it's important to note that if the task is created on May 11th and should be ran every 3 days, then it will run the next day on the 12th and then 3 days after that.
After we add the directory to the AUTOLOAD
list, we can run the schedule:run
command which will find the command and execute it.
Masonite will fetch all tasks from the container by finding all subclasses of scheduler.tasks.Task
, check if they should run and then either execute it or not execute it.
Even though we ran the task, we should not see any output. Let's change the task a bit by printing "Hi" and setting it to run every minute:
Now let's run the command again:
We should now see "Hi!" output to the terminal window.
You may also run a specific task by running the schedule:run command with a --task flag. The flag value is the container binding (usually the task class name):
Or you can give your task a name explicitly:
and then run the command by name
Setting up Cron Jobs are for UNIX based machines like Mac and Linux only. Windows has a similar schedule called Task Scheduler which is similar but will require different instructions in setting it up.
Although the command above is useful, it is not very practical in a production setup. In production, we should setup a cron job to run that command every minute so Masonite can decide on what jobs need to be ran.
We'll show you an example cron job and then we will walk through how to build it.
When a cron job runs, it will typically run commands with a /bin/sh command instead of the usual /bin/bash. Because of this, craft may not be found on the machine so we need to tell the cron job the PATH that should be loaded in. We can simply find the PATH by going to our project directory and running:
Which will show an output of something like:
If you are using a virtual environment for development purposes then you need to run the env
command inside your virtual environment.
We can then copy the PATH and put it in the cron job.
To enter into cron, just run:
and paste the PATH
we just copied. Once we do that our cron should look like:
Exit out of nano. Now we just need to setup the actual cron job:
Now we just need to setup the cron task itself. This could be as easy as copying it and pasting it into the nano editor again. You may need to change a few things though.
The first * * * * *
part is a must and bascially means "run this every minute by default". The next part is the location of your application which is dependant on where you installed your Masonite application.
The next part is dependant on your setup. If you have a virtual environment then you need to activate it by appending && source venv/bin/activate
to the cron task. If you are not running in a virtual environment then you can leave that part out.
Lastly we need to run the schedule command so we can append && craft schedule:run
Great! Now we have a cron job that will run the craft command every minute. Masonite will decide which classes need to be executed.
Environments in Masonite are defined in .env
files and contain all your secret environment variables that should not be pushed into source control. You can have multiple environment files that are loaded when the server first starts. We'll walk through how to configure your environment variables in this documentation.
Never load any of your .env files into source control. .env
and .env.*
are in the .gitignore
file by default so you should not worry about accidentally pushing these files into source control.
Masonite comes with a LoadEnvironment
class that is called in the bootstrap/start.py
file. This file in imported into the wsgi.py
file which is where the execution of the environment actually happens because of the import.
You likely won't have to use this class since this class handles most use cases by default but we will go over how the class itself works.
In bootstrap/start.py
you will see a code that looks something like:
This class instantiation does a few things:
The first thing is it loads the .env file located in the base of your application into the Python environment. If you installed Masonite using craft install then Masonite automatically create this .env
file for you based on the .env-example
file. If you have installed Masonite but do not see this .env
file then you can create it manually and copy and paste the contents of .env-example
.
The next thing it will do is look for an APP_ENV
variable inside your .env
file it just loaded and then look for an environment with that value.
For example, this variable:
Will load additionally load the .env.local
environment file.
This may be useful to have more global environment variables that can be shared across your team like Stripe, Mailgun, or application keys and then have more developer specific values like database connections, Mailtrap or different storage drivers for development.
In addition to loading the .env
file and the additional environment file defined in your .env
file, you can load a third environment by specifying it in the constructor:
This will load the .env
file, the .env.local
file and the .env.development
environment file.
If you don't want to load an additional environment and instead want to load only 1 single environment then you can pass in the only
parameter.
This will load only the .env.development
environment file.
Environment variables should be set on a project per project basis inside your .env file. When the server starts, it will load all of those environment variables into the current global environment. You can fetch these environment variables 1 of 2 ways:
You can obviously get them in the normal Python way by doing something like:
Notice that the above example is a string. We typically need the data type to be casted to the respective type. For example we need 5432
to be an integer and need True
to be a boolean.
We can use the env()
function in order to accomplish this which takes the place of os.getenv()
. This looks like:
If the value is a numeric then it will cast it to an integer. Below are the examples of what this function will cast:
If you do not wish to cast the value then pass in false as the third parameter:
Masonite testing is very simple. You can test very complex parts of your code with ease by just extending your class with a Masonite unit test class.
Although Masonite uses pytest
to run tests, Masonite's test suite is based on unittest
. So you will use unittest
syntax but run the tests with Pytest. Because of this, all syntax will be in camelCase
instead of PEP 8 under_score
. Just know that all TestCase
method calls used during testing is in camelCase
form to maintain unittest standards.
Normal tests should still be underscore and start with test_
though like this:
First, create a new test class in a testing directory. There is a craft command you can run to create tests for you so just run:
This will create a user test for us which we can work on. You can drag this test in any subdirectory you like.
This command will create a basic test like the one below:
That's it! You're ready to start testing. Read on to learn how to start building your test cases.
Most times you want to develop and test on different databases. Maybe you develop on a local MySQL database but your tests should run in a SQLlite database.
You can create a .env.testing
file and put all database configs in that. When Pytest runs it will additionally load and override any additional environment variables.
Your .env.testing
file may look like this:
Feel free to load any testing environment variables in here. By default they will not be commited.
Here is a list of methods that can be used for assetions.
All methods that begin with assert
can be chained together to run through many assertions. All other method will return some kind of boolean or value which you can use to do your own assertions.
We have a few options for testing our routes.
To check if a route exists, we can simple use either get or post:
The request and responses of a test are gotten by accessing the request
and response
attributes. The response
attribute will be a string representation of your route:
You can choose anyone of the normal request methods:
You can use a standard JSON request and specify whichever option you need using the json()
method:
This can be used to see if the template returned a specific value
You can also use:
You can easily check if the response is ok by using the ok
method:
By default, all calls to your routes with the above methods will be without CSRF protection. The testing code will allow you to bypass that protection.
This is very useful since you don't need to worry about setting CSRF tokens on every request but you may want to enable this protection. You can do so by calling the withCsrf()
method on your test.
This will enable it on a specific test but you may want to enable it on all your tests. You can do this by adding the method to your setUp()
method:
As you have noticed, Masonite has exception handling which it uses to display useful information during development.
This is an issue during testing because we wan't to be able to see more useful testing related issues. Because of this, testing will disable Masonite's default exception handling and you will see more useful exceptions during testing. If you want to use Masonite's built in exception handling then you can enable it by running:
You can get the output by using the capture output easily by calling the captureOutput
method on your unit test:
A lot of times you will want to build tests around your API's. There are quite a few methods for testing your endpoints
You can test to make sure your endpoint returns a specific amount of something. Like returning 5 articles:
You can also use assertCount(5)
:
You can also use amount
which is just an alias for count
:
You can also check if a specific JSON key has a specific amount. For example:
Sometimes you will want to check if your endpoint returns some specific JSON values:
You can also specify a dictionary of values as well and will check if each value inside the response:
You do not have to specify all of the elements. Just the ones you want to check for.
You can alo use:
You can also assert values inside a list of responses:
You can also use dot notation for multi dimensional endpoints:
Sometimes you don't want to use dot notation and may choose to convert directly to a dictionary and assert values on that. This is also good for debugging so you can print the dictionary to these terminal. You can do this easily:
You can test if a specific parameter contains a specific value. For example if you want to see if the parameter id
is equal to 5
:
You can test if a specific header contains a specific value. For example if you want to see if the header Content-Type
is equal to text/html
:
You can use the isStatus
and assertIsStatus
methods to assert status checks:
You can also easily assert 404
methods:
This is the same as asserting a 404
status code.
By default, Masonite turns off subdomains since this can cause issues when deploying to a PaaS that deploys to a subdomain like sunny-land-176892.herokuapp.com
for example.
To activate subdomains in your tests you will have to use the withSubdomains()
method. You can then set the host in the wsgi
attribute.
By default, to prevent messing with running databases, database test cases are set to only run on the sqlite
database. You can disable this by setting the sqlite
attribute to False
.
This will allow you to use whatever database driver you need.
By default, all your tests will run inside a transaction so any data you create will only exist within the lifecycle of the test. Once the test completes, your database is rolled back to its previous state. This is a perfect way to prevent test data from clogging up your database.
Although this is good for most use cases, you may want to actually migrate and refresh the entire migration stack. In this case you can set the refreshes_database
attribute to True
.
Now this will migrate and refresh the database.
Beware that this will destroy any database information you have.
Factories are simply ways to seed some dummy data into your database. You can create a factory by making a method that accepts a faker argument and using that to seed data.
Masonite has a convenient method you can use that will run once the test first boots up called setUpFactories
. This will run once and only once and not between every test.
Let's create the factory as well as use the setUpFactories method to run them now.
Below is how to create 100 users:
You don't need to build factories though. This can be used to simply create new records:
We can load users into the route and check if they can view the route. This is good to see if your middleware is acting good against various users. This can be done with the acting_as()
method.
Maybe you need to check a post request and pass in some input data like submitting a form. You can do this by passing in a dictionary as the second value to either the get
or post
method:
The same can be applied to the get method except it will be in the form of query parameters.
Instead of doing model calls to check if values exist in the database, you can use a simple assertDatabaseHas
assertion:
You can also do the same thing with the opposite assertDatabaseNotHas
assertion:
To complete our test, let's check if the user is actually created:
Thats it! This test will now check that the user is created properly
You can run tests by running:
Selenium tests are browser tests. With normal unit testing you are usually testing sections of your code like JSON endpoints or seeing a header on a page.
With Selenium testing, you are able to test more advanced features that may require JS handling like what happens when a user clicks a button or submits or a form.
With selenium testing, you will build a bunch of actions for your tests and a browser will open and actually go step by step according to your instructions.
This package comes shipped with all the required selenium drivers so there is no need for any external selenium server installations.
First we will need to install the package:
This package extends the current testing framework so you will add the TestCase to your current unit tests.
Let's build a new TestCase
and test the homepage that shows when Masonite is first installed:
This will generate a new test that looks like this:
In order to build the selenium test we need to import the class and add it to our TestCase
.
We now have all methods available to us to start building our selenium tests.
First before we build our TestCase, we need to specify which browser we want. The current 2 options are: chrome
and firefox
.
You can specify which browser to use using the useBrowser()
method inside the setUp()
method.
If you are using the chrome driver you can optionally specify which version to run.
You can optionally specify if you want to run your browsers in a headless state. This means that the browser will not actually open to run tests but will run in the background. This will not effect your tests but is just a preference and usually your tests will run faster.
Here is a basic example on building a test for the installed homepage:
we can then run the test by running:
You can chain all methods together to build up and mock user actions. An example test might look like this:
When finding a selector you can use a few symbols to help navigate the page
Take this form for example:
You can select an ID by using the #
symbol:
You can select by the name by simply passing in the name value. This will default to the name attribute:
The issue with selecting by a normal selector like an ID or a name is that these could change. This is why you are able to select with a unique attribute name.
You may change your form a bit to do something like this instead:
You can then tell Masonite what the name of your unique attribute is:
and finally you can select by that attribute using the @
symbol:
Below are the available methods you can use to build your tests.
This method will navigate to a URL
If the URL does not start with http then Masonite will prepend the APP_URL
environment variable to the front. If this is running inside your Masonite application, you can change this value in your .env
file.
This method will assert that the title is a specific value
Opposite of assertTitleIs
.
This method will assert that the current URL is a specific value
Asserts that something is available on the page that the user can see
Just an alias for assertSee
.
Opposite of assertCanSee
. Used to assert that text is not on the page.
Types text into a text box.
You can choose an option in a select box by its value:
This will check a checkbox
Resizes the window based on a width and heigh parameter
Maximizes the window
Minimizes the window
Refreshes the window
Navigates backwards
Navigates forwards
Clicks a link on a page
Alias for link
.
Submits the current form the last entered element is in
You can also submit another form by entering a selector
Clicks an element
Closes the browser
Masonite Notifications can easily add new notification sending semantics for your Masonite project. These notifications could be sending an email or a slack message. This package is designed to be extremely simple and modular so allow new notification abilities to be added to it through third party integrations.
In order to get started using Masonite Notifications, we first have to pip install it:
And then add the provider to our PROVIDERS
list:
Thats it! Let's see how it works!
There are a few concepts we'll need to cover so you can fully understand how Notifications work. First we will cover the high level stuff and then slowly work down into the lower level implementations. For the purposes of this documentation, we will walk through how to setup a welcome notification so we can send an email when a user signs up.
In order to use it we can create a notification class. We can do this simply with a new craft command.
This will create a notification in the app/notifications
directory. Feel free to move it wherever you like though.
This will create a class like so:
Let's now walk through how to build a notification so we can send the email to our user.
Since our notification inherits from Notifiable
, we have access to a few methods we will use to build the notification. We'll show a final product of what it looks like since it's pretty straight forward but we'll walk through it after:
Notice here we are calling a few methods such as driver
, panel
, line
, etc. If we send this message it will look like:
Not bad. We can use this logic to easily build up emails into a nice format simply.
Let's walk through the different options to build an email notification and what they do.
Now that we have built our notification above, we can send it in our controller (or anywhere else we like):
Notice here we simply specified the Notify class in the parameter list and we are able to pass in our awesome new WelcomeNotification into the mail method.
NOTE: The method you should use on the notify class should correspond to the method on the notification class. So for example if we want to execute the slack method on the WelcomeNotification then we would call :
The method you call should be the same as the method you want to call on the notification class. The Notify
class actually doesn't contain any methods but will call the same method on the notification class as you called on the Notify
class.
You can also send multiple notifications and notification types with the via
and send
method like so:
This is useful for sending mail more dynamically or just sending multiple types of notifications in 1 line.
If you would like to queue the notification then you just need to inherit the ShouldQueue
class and it will automatically send your notifications into the queue to be processed later. This is a great way to speed up your application:
Out of the box, Masonite notifications comes with Slack support as well in case we want to send a message to a specific slack group.
NOTE: In order to use this feature fully, you'll need to generate a token from Slack. This token should have at minimum the channels:read
, chat:write:bot
, chat:write:user
and files:write:user
permission scopes. If your token does not have these scopes then parts of this feature will not work.
Going back to our WelcomeNotification, we can simply specify a new method called slack
.
Notice the new slack method at the bottom. we will use this method to build our slack notification. Again we will show you a full example and then run through all the methods:
Now that we have built our notification above, we can send it in our controller (or anywhere else we like):
Notice here we simply specified the Notify
class in the parameter list and we are able to pass in our awesome new WelcomeNotification into the slack method.
The Notifiable
class is very modular and you are able to build custom integrations if you like pretty simply. In this section we'll walk through how to create what are called Components
.
Components are classes that can be added to a Notification class that extend the behavior of the notification. In fact, the Notifiable class is just a simple abstraction of two different components. Let's look at the signature of the class that we have been inheriting from.
The Component classes are the classes that have our methods we have been using. If you'd like to see the source code on those components you can check them out on GitHub to get a lower level understanding on how they work.
Let's walk through a bit on how we created our MailComponent by creating a simplified version of it. First let's create a simple class:
Now let's add a line and a subject method to it:
and let's use these two methods to build a template attribute
Since we returned self we can keep appending onto the notification class like we have been.
The actual MailComponent class is a bit more complex than this but we'll keep this simple for explanatory purposes.
Whenever we insert the notification into the Notify class:
This will call the mail method on the notification class (or whatever other method we called on the Notify class).
Once that is returned then it will call the fire_mail method which you will specify in your component.
If you are created a discord notification then you should have a fire_discord
method on your component and you will call it using notify.discord(WelcomeNotification)
.
Since we want to call the mail method on it, we will create a fire_mail
method:
Sometimes you will want to pass in data into the fire_mail
method. In order to keep this simple and modular, any keyword arguments you pass into the Notify class will be set on the notification class as a protected member. For example if we have this:
It will set a _to
attribute on the notification class BEFORE we get to the fire method.
So using the example above we will be able to do:
We can use this behavior to pass in information into the fire_mail
method while keeping everything clean.
A practical example is sending the message to a certain user:
Notice here we now have a _to
member on our class we can use because we passed it through from our Notify
class.
Ok so finally we have enough information we need to send the actual email. The fire_method is resolved by the container so we can simply specify what we need to send the email.
Our notification class will look like:
Our Notify class will look like:
and our fire method will look like:
Remember the _to
class attribute that came from the keyword argument in the Notify
class.
Masonite uses an extremely powerful pattern commonly known as the Manager Pattern (also known as the Builder Pattern). Because Masonite uses classes with the XManager
namespace, we will call it the Manager Pattern throughout this documentation.
Think of the Manager Pattern as attaching a Manager to a specific feature and responsible for managing a specific set of drivers. These managers are responsible for instantiating FeatureXDriver
classes. For example, we attach a UploadManager
to the upload feature. Now the UploadFeature
will instantiate UploadXDriver
classes.
For an actual example inside Masonite, there are currently two classes for the Upload feature: UploadDiskDriver
and UploadS3Driver
. Whenever we set the DRIVER
in our config/storage.py
file to s3
, the UploadManager
will use the UploadS3Driver
to store our files.
This is extremely useful for extending functionality of the managers. If we need to upload to Google, we can just make a UploadGoogleDriver
and put it inside the container. If we set our configuration DRIVER
to google
, our UploadManager
will now use that class to store files.
Masonite obviously comes with several managers such as the UploadManager
and the MailManager
. Let's walk through how to create a new manager called the TaskManager
.
Managers can live wherever they want but if you are developing a manager for the Masonite core package, they will be placed inside masonite/managers
.
Let's create a new file: masonite/managers/TaskManager.py
.
Great! Now all managers should inherit from the masonite.managers.Manager
class. Our TaskManager
should look something like:
Awesome! Inheriting from the Manager class will give our manager almost all the methods it needs. The only thing we need now is to tell this manager how to create drivers. So to do this all we need are two attributes:
Perfect. Managers are both extremely powerful and easy to create. That's it. That's our entire manager. The config attribute is the configuration file you want which via the key in the container and the driver_prefix
is the drivers you want to manage. In this case it is the Task{X}Driver
. This manager will manage all drivers in the container that conform to the namespaces of Task{0}Driver
like TaskTodoDriver
and TaskNoteDriver
.
Notice that the config is TaskConfig
and not task
. This attribute is the binding name and not the config name. We can bind the task
config into the container like so:
Which will be required to use our new task manager since it relies on the task configuration. You can do this inside the Service Provider that will ship with this manager. We will create a Service Provider later on but for now just know that that's where that configuration comes from.
Great! We can put this Service Provider in our app/application.py
file inside the PROVIDERS
list. Once that is inside our providers list we can now use our new manager:
Although the above code works fine it might be more useful to add a container swap in our Service Provider so we can resolve an arbitrary shorthand class which will return our correct driver. We can do this in our boot method:
Now we can resolve this Task
class which will return the correct driver since we specified a driver swap:
Logging is a pretty crucial part of any application. The Masonite Logging package allows you to see errors your application is throwing as well as allow you to log your own messages in several different alert levels.
Masonite Logging currently contains the ability to log to a file, syslog and slack.
To get the Masonite Logging package up and running on your machine you must first install the package:
And then add the Service Provider to your application:
You can then publish the provider which will create your config/logging.py
configuration file:
The Masonite Logging package will automatically register an exception listener with your Masonite application and log any exceptions your application encounters with the corresponding channel.
The Masonite Logging package uses the concept of channels and drivers. A channel internally is used to create and pass various information to instantiate the driver. At a higher level, you will mainly be working with channels.
Out of the box there are several different channels: single
, stack
, daily
, slack
, syslog
and terminal
. Each channel will handle logging messages in it's own way.
The single channel will put all information in a "single" file. This file can be specified in your config/logging.py
file in the path
option:
The daily channel is similiar to the single channel except this will create a new log file based on the current day. So this will create log files like 10-23-2019.log
and 10-24-2019.log
.
The path to set here needs to be a directory instead of a path to a file:
The Slack channel will send messages directly to your Slack channel so you or your team can act quickly and be alerted directly of any messages logged.
You'll need to generate a Slack application token. You can add it to your .env
file:
You then need to set a few options in your config file if you need to change any default settings like the user, the icon emoji,
These options are up to you.
The terminal channel will simply output errors to the terminal. This is handy for debugging or in addition to other channels when using the stack
channel.
The stack channel is useful when you need to combine several channels together. Maybe you want it to log to both a daily
channel file as well as send a message to your slack group. You can do this easily by specifying the channels
within your stack
channel options.
You can have as many channels as you want here.
The syslog
channel will tie directly into your system level logging software. Each operating system has its own type of system monitoring.
You'll need to tie into your system socket path. This can be different per operating system and machine so find yours and put that socket path in the config options:
Log levels are a hierarchy of log levels that you can specify on your channels. The hierarchy in order of most important to least important are:
Each channel can have its own minimum log level. The log message will only continue if it is greater than or equal to the minimum log level on that channel.
For example, if we have a configuration like this on the daily
channel:
This will only send messages to this channel if the log level is notice or above. It will ignore all debug
level log messages.
You can of course write your own log messages. You can resolve the logger class from the container and use a method equal to the log level you want to write the message for.
You can easily switch channels by using the channel
method:
By default, Masonite Logging will record all times in the UTC
timezone but in the event you want to switch timezones, you can do so in your configuration file:
All timestamps associated with logging will now use the correct timezone.
Masonite 2.0.0+
Installing Masonite Billing is simple. We just need a new configuration file, 2 new migrations and extend our User model.
Pip Install
Simply add the Masonite Billing Service Provider to your providers list:
This will add a new install:billing command to craft. Just run:
This will create a new configuration file in config/billing.py
All billing information will be located in the config/billing.py file and has 2 important constants:
The DRIVER is the processor that Masonite billing will use and the DRIVERS constant is the configuration setting for the driver.
Although there is the DRIVER constant, Masonite Billing currently only supports Stripe. Other drivers like Braintree will be supported in later releases which should be backwards compatible.
We'll need to create 2 new migrations: one to add columns to the users table and one migration to create a new subscriptions table. Just create these migration files with craft and copy and paste the migration into those files and migrate them.
Let's first add 2 new columns to our users table.
Now just add this column to the migration file:
Now let's add a new subscriptions table.
Now just migrate the new migrations:
Just add your Stripe public and secret keys to your .env file:
Masonite Billing consists of a model that should be inherited by whatever model you want to add subscription billing information to. In this example here, we will focus on adding the billing integration to our User model.
Once that is added we will now have a plethora of methods we can use to subscribe and charge our user.
Read more about how to handle subscription and payment information in the Usage documentation.
Below you will notice we are using a tok_amex token, you may use this token for testing purposes but this token in production should be the token returned when processing your stripe form.
It's also important to note that the subscription records in your database are never deleted but are updated instead. So canceling a subscription does not delete that subscription from your database but only sets when the subscription ended. This is good if you want to dump the data into an email campaign tool to try and get back any lost customers.
To subscribe a user to plans, we can use the subscribe method like so:
This method retuns a string of the subscription token such as sub_j8sy7dbdns8d7sd..
If you try to subscribe a user to a plan and the plan does not exist in Stripe then Masonite will throw a billing.exceptions.PlanNotFound
exception.
If you want to check if the user is subscribed you have a few options:
You may check if the user is subscribed to any plan:
or you can check if the user is subscribed to a specific plan:
You may also check if a user was subscribed but their plan has expired:
or you can obviously check if the user was subscribed to a specific plan:
If you need to get the name of the plan the user is on you can do so using the plan() method:
This will return the name of the plan in Stripe, not the plan ID. For example, our plan ID might be masonite-test but the plan name could be "Awesome Plan."
If the plan you are subscribing a user to has a trial set inside Stripe then the user will be automatically enrolled in a trial for that time. We can check if the user is on a trial using the on_trial() method. In our examples here, the masonite-test plan has a 30 day free trial set inside Stripe.
For example:
We may also want to skip the trial and charge the user immediately for the plan:
We can also specify the amount of days the user should be on a trial for when subscribing them to a plan:
We can swap subscription plans at anytime using the swap() method:
If you want to cancel a user's subscription we can use the cancel() method.
This will cancel the users plan but continue the subscription until the period ends.
Notice here that the last is_subscribed() method still returned True even after we canceled. The user will continue the subscription until the period ends. For example if it is a 1 month subscription and the user canceled 1 week in, Masonite Billing will continue the subscription for the remaining 3 weeks.
If you wish to cancel the user immediately we can specify the now=Trueparameter:
The period between canceling a subscriptions and the period running out can be caught using the user.is_canceled() method:
Again this will only return true if the user has an active subscription but has chosen to cancel it. If the subscription is canceled immediately, this will return False.
If you don't cancel a plan immediately, there will be a period between when the user canceled and when the plan can be resumed. We can use the resume() method here:
Sometimes a user will want to switch or update their card information. We can use the card() method and pass a token to it:
If you want to make one off transactions for customers you can do so easily using the charge() method:
You can charge a card by passing a token:
You can also charge a customer directly by leaving out the token which will charge whatever card the customer has on file.
You can also add a description and metadata (as a dictionary) for the charge:
You will first need to setup coupons in Stripe.
Once you setup a coupon you can use coupons on both charges and subscriptions:
You can also pass in an integer to deduct an amount:
This will deduct 5 dollars frin the 10 dollars you are charging the user.
You can also make a coupon for a certain percentage reduction:
This will deduct 25 percent off.
Webhooks allow your application to interact directly with stripe when certain actions happen such as when a user's subscription has expired. You can use these webhooks to catch any events emitted.
Masonite Billing also allows you to tie into Stripe webhooks. If the subscription is cancelled in Stripe, Stripe will send your server a webhook and Masonite Billing will update the database accordingly.
Be sure to setup the correct url in the the Stripe dashboard inside your Webhook Settings. The url to setup should be http://your-domain.com/stripe/webhook
or if you're testing with Ngrok it should be something like http://684b1285.ngrok.io/stripe/webhook
.
If you go the Ngrok route be sure to read the Testing With Ngrok section
We can simply put the webhook controller in our routes/web.py file just like any other controller but it can point to the packages controller:
This will send all Stripe traffic to the server and handle any hooks accordingly.
Most developers use Ngrok for testing incoming webhooks. Ngrok is a freemium HTTP tunneling service that allows extrernal requests to tunnel into your localhost and port environment. It's exellent for testing things like this as well as other things like OAuth.
If you use Ngrok you will get a subdomain like: http://684b1285.ngrok.io
. Because this is a subdomain, Masonite needs to know which subdomain to support so you're route will have to be:
in order to catch the Ngrok subdomain. This setup will allow you to send test webhooks from Stripe to your server.
You can read more about subdomains in the Subdomain Routing section of the Routing documentation.
External incoming API calls typically will not be able to be CSRF protected because they will not know the specific token connected to a request. We will need to put an exception to the /stripe/webhook route:
Currently the webhook controller only handles when a subscription is canceled but can handle any hook you like. If you need to create a custom hook you can inherit from the WebhookController and add the needed controller methods:
You'll also have to specify a new location of your controller which should now be located in your normal controllers directory:
Events are specific types of webhooks that Stripe will send out when certain actions occur.
You can see a list of stripe events here: https://stripe.com/docs/api#event_types
You'll notice that we have a handle_resource_event method. The WebhookController will take all incoming webhook events into the handle method and dispatch them to other methods.
How it does this is simply takes the event lets say the charge.dispute.created Stripe event (see the link above for a list of more events) and parses it into a string that looks like:
You'll notice we just replaced the . with _ and prefixed a handle to it. This looks like a method call. If we simply create a method now:
This method will be called whenever we receive a webhook for that event. If no method is found for the incoming webhook then the server will return a Webhook Not Supported string which you may see while development testing your Stripe webhooks.
Contracts are used when creating drivers to ensure they conform to Masonite requirements. They are a form of interface in other languages where the child class is required to have the minimum number of methods needed for that driver to work. It is a promise to the class that it has the exact methods required.
Contracts are designed to follow the "code to an interface and not an implementation" rule. While this rule is followed, all drivers of the same type are swappable.
Drivers are designed to easily switch the functionality of a specific feature such as switching from file system storage to Amazon S3 storage without changing any code. Because each driver someone creates can technically have whatever methods they want, this creates a technical challenge of having to change any code that uses a driver that doesn't support a specific method. For example a driver that does not have a store
method while other drivers do will throw errors when a developer switches drivers.
Contracts ensure that all drivers of a similar type such as upload, queue and mail drivers all contain the same methods. While drivers that inherit from a contract can have more methods than required, they should not.
If your driver needs additional methods that can be used that are now inside a contract then your documentation should have that caveat listed in a somewhat obvious manner. This means that by the developer using that new method, they will not be able to switch to other drivers freely without hitting exceptions or having to manually use the methods used by the driver.
Therefore it is advisable to not code additional methods on your drivers and just keep to the methods provided by the base class and contract.
Contracts are currently used to create drivers and are located in the masonite.contracts
namespace. Creating a driver and using a contract looks like:
Now this class will constantly throw exceptions until it overrides all the required methods in the class.
It is useful if you want to "code to an interface and not an implementation." This type of programming paradigm allows your code to be very maintainable because you can simply swap out classes in the container that have the same contract.
For example, Masonite has specific manager contracts depending on the type of driver you are trying to resolve. If we are trying to get the manager for the upload drivers, we can resolve that manager via the corresponding upload manager:
Notice this simply returns the specific upload manager used for uploading. Now the upload manager is not a "concrete" implementation but is very swappable. You can load any instance of the the UploadManagerContract
in the container and Masonite will fetch it for you.
There are several contracts that are required when creating a driver. If you feel like you need to have a new type of driver for a new feature then you should create a contract first and code to a contract instead of an implementation. Below are the types of contracts available. All contracts correspond to their drivers. So an UploadContract
is required to create an upload driver.
is a package designed to make it dead simple to add externally facing API's with various types of authentication and permission scopes. There is a new concept called "API Resources" which you will use to build your specific endpoint. In this documentation we will walk through how to make a User Resource so we can walk through the various moving parts.
Just run:
We can create API Resources by building them wherever you want to. In this documentation we will put them in app/resources
. We just need to create a simple resource class which inherits from api.resources.Resource
.
You should also specify a model by importing it and specifying the model attribute:
Lastly for simplicity sake, we can specify a serializer. This will take any Orator models or Collection we return and serialize them into a dictionary which will be picked up via the JsonResponseMiddleware
.
Awesome! Now we are ready to go!
Our resource has several routes that it will build based on the information we provided so let's import it into our web.py file so it will build the routes for us. We can also specify the base route which will be used for all routes.
This will build a route list that looks like:
We can also specify the routes that we want to create by setting the methods
attribute:
This will only build those routes dependent on the methods specified:
You can easily add middleware to routes by specifying them using the middleware
method:
Or of course you can add the middleware to a group:
You may want to override some methods that are used internally by the API endpoint to return the necessary data.
The methods are: create, index, show, update, delete.
You can check the repo on how these methods are used and how you should modify them but it's pretty straight forward. The show method is used to show all the results are is what is returned when the endpoint is something like /api/user
.
Overriding a method will look something like:
This will not only return all the results where active is 1
. Keep in mind as well that these methods are resolved via the container so we can use dependency injection:
The index method is ran when getting all records with: POST /api/user
.
Currently our response may look something like:
You might not want to display all the model attributes like id
, email
or password
. We can choose to remove these with the without
class attribute:
Now our response will look like:
Yes, it's that easy.
For any API package to be awesome, it really needs to have powerful and simple authentication.
We can specify our authentication for our specific endpoint by inheriting from a class:
Now our endpoint is being JWT Authentication. If we hit our endpoint now in the browser by sending a GET request to http://localhost:8000/api/user
. we will see:
We can easily create an endpoint for giving out and refreshing API tokens by adding some routes to our web.py:
Now we can make a POST request to http://localhost:8000/token
with your username and password which will give us back a JWT token.
POST
http://localhost:8000/token
Use this endpoint to retrieve new tokens using a username and password. The username and password will default to your regular authentication model located in config/auth.py
.
we can now use this token to make our calls by using that new token. This token is good for 5 minutes and it will be required to refresh the token once expired.
Once our JWT token expires, we need to refresh it by sending our old expired token to
POST
http://localhost:8000/token/refresh
You can also specify any permission scopes you need. Most of the time some API endpoints will need to have more restrictive scopes than others. We can specify whatever scopes we need to with the scopes
attribute as well as inheriting another class.
Now all API endpoint will need to have the correct permission scopes. Hitting this API endpoint now will result in:
We can request the scopes we need by spending a POST request back to the endpoint with a scopes input. The scopes should be comma separated. The request should look like:
This will generate a new token with the correct permission scopes.
Filter scopes is an extension of the scopes above. It will filter the data based on the scope level. This is useful if you want a specific scope to have more permission than other scopes.
To do this we can extend our resource with the FilterScopes
class:
Now when you make this request it will return the columns in accordance with the user scopes.
Authentication classes are extremely simple classes. They just need to inherit the BaseAuthentication class and contain 2 methods: authenticate
and get_token
.
This method is resolved via the container. it is important to note that if the authenticate is successful, it should not return anything. Masonite will only look for exceptions thrown in this method and then correlate an error response to it.
For example if we want to return an error because the token was not found, we can raise that exception:
Which will result in an error response:
This method is used to return a dictionary which is the decrypted version of the token. So however your authentication class should decrypt the token, it needs to do so in this method. This all depends on how the token was encrypted in the first place. This may look something like:
Serializers are simple classes with a single serialize
method on them. The serialize
method takes a single parameter which is the response returned from one of the create, index, show, update, delete methods.
For example if we return something like a model instance:
We will receive this output into our serialize method:
which we can then serialize how we need to and return it. Here is an example of a JSON serializer:
notice we take the response and then convert that response into a dictionary depending on the response type.
Once we convert to a dictionary here, the JSONResponseMiddleware
will pick that dictionary up and return a JSON response.
We have access to a few builtin methods from anywhere in our resource.
The token can be in several different forms coming into the request. It can be either a JWT token or a regular token or some other form of token. Either way it needs to be "unencrypted" in order for it to be used and since the authentication class is responsible for un-encrypting it, it is the responsibility of the authentication class to get the token.
This method is only available when inheriting from an authentication class like JWTAuthentication
which requires this method and should return the un-encrypted version of the token
There are a few locations the token can be. The two main locations are in the query string itself (/endpoint?token=123..
) or inside a header (the HTTP_AUTHORIZATION
header). We can get the token regardless of where it is with the fetch_token()
method:
This section of the documentation will contain various tutorials. These are guides that are designed to take you from beginning to end on building various types of projects with Masonite. We may not explain things in much detail for each section as this part of the documentation is designed to just get you familiar with the inner workings of Masonite.
Since this section of the documentation is designed to just get you up and coding with Masonite, any further explanations that should be presented are inside various "hint blocks." Once you are done with the tutorial or simply want to learn more about a topic it is advised that you go through each hint block and follow the links to dive deeper into the reference documentation which does significantly more explaining.
You will see various hint blocks throughout the tutorials. Below are examples of what the various colors represent.
You'll see hint blocks that are green which you should follow if you want to learn more information about the topic currently being discussed.
You'll also see hint blocks that are blue. These should not be ignored and typically contain background information you need to further understand something.
This tutorial will assume you have already installed Masonite. If you haven't, be sure to read the guide to get a fresh install of Masonite up and running. Once you have one up and running or if you already have it running, go ahead and continue on.
In this tutorial we will walk through how to create a blog. We will touch on all the major systems of Masonite and it should give you the confidence to try the more advanced tutorials or build an application yourself.
Typically your first starting point for your Masonite development flow will be to create a route. All routes are located in routes/web.py
and are extremely simple to understand. They consist of a request method and a route method. Routing is simply stating what incoming URI's should direct to which controllers.
For example, to create a GET
request route it will look like:
We'll talk more about the controller in a little bit.
We will start off by creating a view and controller to create a blog post.
A controller is a simple class that holds controller methods. These controller methods will be what our routes will call so they will contain all of our application's business logic.
Think of a controller method as a function in the views.py
file if you are coming from the Django framework
Let's create our first route now. We can put all routes inside routes/web.py
and inside the ROUTES
list. You'll see we have a route for the home page. Let's add a route for creating blogs.
You'll notice here we have a BlogController@show
string. This means "use the blog controller's show method to render this route". The only problem here is that we don't yet have a blog controller.
All controllers are located in the app/http/controllers
directory and Masonite promotes 1 controller per file. This has proven efficient for larger application development because most developers use text editors with advanced search features such as Sublime, VSCode or Atom. Switching between classes in this instance is simple and promotes faster development. It's easy to remember where the controller exactly is because the name of the file is the controller.
You can of course move controllers around wherever you like them but the craft command line tool will default to putting them in separate files. If this seems weird to you it might be worth a try to see if you like this opinionated layout.
Like most parts of Masonite, you can scaffold a controller with a craft command:
This will create a controller in app/http/controllers
directory that looks like:
Simple enough, right? You'll notice we have a show
method. These are called "controller methods" and are similiar to what Django calls a "view."
Notice we now have our show method that we specified in our route earlier.
We can return a view from our controller. A view in Masonite are html files or "templates". They are not Python objects themselves like other Python frameworks. Views are what the users will see.
This is important as this is our first introduction to Python's IOC container. We specify in our parameter list that we need a view class and Masonite will inject it for us:
Notice here we annotated the View
class. This is what Masonite call's "Auto resolving dependency injection". If you don't like the semantics of this there are other ways to "resolve" from the container that you will discover in the reference documentation but for now let's stay with this method of resolving.
You'll notice now that we are returning the blog
view but it does not exist yet.
All views are in the resources/templates
directory. We can create a new file called resources/templates/blog.html
or we can use another craft command:
This will create that template we wanted above for us.
We can put some text in this file like:
and then run the server
and open up localhost:8000/blog
, we will see "This is a blog"
Most applications will require some form of authentication. Masonite comes with a craft command to scaffold out an authentication system for you. This should typically be ran on fresh installations of Masonite since it will create controllers routes and views for you.
For our blog, we will need to setup some form of registration so we can get new users to start posting to our blog. We can create an authentication system by running the craft command:
We should get a success message saying that some new assets were created. You can check your controllers folder and you should see a few new controllers there that should handle registrations.
We will observe what was created for us in a bit.
In order to register these users, we will need a database. Hopefully you already have some kind of local database setup like MySQL or Postgres but we will assume that you do not. In this case we can just use SQLite.
Now we just need to change a few environment variables so Masonite can create the SQLite database. These environment variable can be found in the .env
file in the root of the project. Open that file up and you should see a few lines that look like:
Go ahead and change those setting to your connection settings by adding sqlite
to the DB_CONNECTION
variable and whatever you want for your database which will be created for you when you migrate. We will call it blog.db
:
Once you have set the correct credentials, we can go ahead and migrate the database. Out of the box, Masonite has a migration for a users table which will be the foundation of our user. You can edit this user migration before migrating but the default configuration will suit most needs just fine and you can always add or remove columns at a later date.
This will create our users table for us along with a migrations table to keep track of any migrations we add later.
Now that we have the authentication and the migrations all migrated in, let's create our first user. Remember that we ran craft auth
so we have a few new templates and controllers.
Go ahead and run the server:
Now that we have our authentication setup and we are comfortable with migrating our migrations, let's create a new migration where we will store our posts.
Our posts table should have a few obvious columns that we will simplify for this tutorial part. Let's walk through how we create migrations with Masonite.
This command simply creates the basis of a migration that will create the posts table. By convention, table names should be plural (and model names should be singular but more on this later).
This will create a migration in the databases/migrations
folder. Let's open that up and starting on line 6 we should see something that looks like:
Lets add a title, an author, and a body to our posts tables.
Now we can migrate this migration to create the posts table
Now that we have our tables and migrations all done and we have a posts table, let's create a model for it.
Models in Masonite are a bit different than other Python frameworks. Masonite uses Orator which is an Active Record implementation of an ORM. This bascially means we will not be building our model and then translating that into a migration. Models and migrations are separate in Masonite. Our models will take shape of our tables regardless of what the table looks like.
Again, we can use a craft command to create our model:
Notice we used the singular form for our model. By default, Orator will check for the plural name of the class in our database (in this case posts) by simply appending an "s" onto the model. We will talk about how to specify the table explicitly in a bit.
The model created now resides inside app/Post.py
and when we open it up it should look like:
Simple enough, right? Like previously stated, we don't have to manipulate the model. The model will take shape of the table as we create or change migrations.
Again, the table name that the model is attached to is the plural version of the model (by appending an "s") but if you called your table something different such as "blog" instead of "blogs" we can specify the table name explicitly:
Orator by default protects against mass assignment as a security measure so we will explicitly need to set what columns we would like to be fillable:
The relationship is pretty straight forward here. Remember that we created a foreign key in our migration. We can create that relationship in our model like so:
Because of how Masonite does models, some models may rely on each other so it is typically better to perform the import inside the relationship like we did above to prevent any possibilities of circular imports.
Let's setup a little HTML so we can learn a bit more about how views work. In this part we will setup a really basic template in order to not clog up this part with too much HTML but we will learn the basics enough that you can move forward and create a really awesome blog template (or collect one from the internet).
Now that we have all the models and migrations setup, we have everything in the backend that we need to create a layout and start creating and updating blog posts.
We will also check if the user is logged in before creating a template.
The template for creating will be located at /blog/create
and will be a simple form for creating a blog post
Notice here we have this strange {{ csrf_field }}
looking text. Masonite comes with CSRF protection so we need a token to render with the CSRF field.
Now because we have a foreign key in our posts table, we need to make sure the user is logged in before creating this so let's change up our template a bit:
auth()
is a globally available function that either returns the current user or returns None
.
For simplicity sake, we won't be styling our blog with something like Bootstrap but it is important to learn how static files such as CSS files work with Masonite so let's walk through how to add a CSS file and add it to our blog.
Firstly, head to storage/static/ and make a blog.css file and throw anything you like in it. For this tutorial we will make the html page slightly grey.
Now we can add it to our template like so right at the top:
That's it. Static files are really simple. It's important to know how they work but for this tutorial we will ignore them for now and focus on more of the backend.
Javascript files are the same exact thing:
Notice that our action is going to /blog/create
so we need to direct a route to our controller method. In this case we will direct it to a store
method.
Let's open back up routes/web.py and create a new route. Just add this to the ROUTES
list:
and create a new store method on our controller:
Now notice above in the form we are going to be receiving 2 form inputs: title and body. So let's import the Post
model and create a new post with the input.
More likely, you will use the request helper and it will look something like this instead:
Also notice we used an input()
method. Masonite does not discriminate against different request methods so getting input on a GET
or a POST
request doesn't matter. You will always use this input method.
Go ahead and run the server using craft serve and head over to localhost:8000/blog
and create a post. This should hit the /blog/create
route with the POST
request method and we should see "post created".
Lets go ahead and show how we can show the posts we just created. In this part we will create 2 new templates to show all posts and a specific post.
Let's create 2 new templates.
Let's start with showing all posts
Let's create a controller for the posts to separate it out from the BlogController
.
Great! So now in our show
method we will show all posts and then we will create a single
method to show a specific post.
Let's get the show
method to return the posts view with all the posts:
We need to add a route for this method:
Our posts view can be very simple:
Go ahead and run the server and head over to localhost:8000/posts
route. You should see a basic representation of your posts. If you only see 1, go to localhost:8000/blog
to create more so we can show an individual post.
Remember we made our author relationship before. Orator will take that relationship and make an attribute from it so we can display the author's name as well:
Let's repeat the process but change our workflow a bit.
Next we want to just show a single post. We need to add a route for this method:
Notice here we have a @id
string. We can use this to grab that section of the URL in our controller in the next section below.
Let's create a single
method so we show a single post.
We use the param()
method to fetch the id from the URL. Remember this key was set in the route above when we specified the @id
For a real application we might do something like @slug
and then fetch it with request().param('slug')
.
We just need to display 1 post so lets just put together a simple view:
Go ahead and run the server and head over the localhost:8000/post/1
route and then localhost:8000/post/2
and see how the posts are different.
By now, all of the logic we have gone over so far will take you a long way so let's just finish up quickly with updating and deleting a posts. We'll assume you are comfortable with what we have learned so far so we will run through this faster since this is just more of what were in the previous parts.
Let's just make an update method on the PostController
:
Since we are more comfortable with controllers we can go ahead and make two at once. We made one that shows a view that shows a form to update a post and then one that actually updates the post with the database.
Remember we made 2 controller methods so let's attach them to a route here:
That should be it! We can now update our posts.
Let's expand a bit and made a delete method.
Notice we used a GET
route here, It would be much better to use a POST
method but for simplicity sake will assume you can create one by now. We will just add a link to our update method which will delete the post.
We can throw a delete link right inside our update template:
Great! You now have a blog that you can use to create, view, update and delete posts! Go on to create amazing things!
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.
Great! If we specify a token by hitting then we will see a different error:
Name | Type | Description |
---|
Name | Type | Description |
---|
You can read more about routes in the documentation
Let's create the BlogController
in the next step:
Be sure to learn more about the .
and head over to and fill out the form. You can use whatever name and email you like but for this purpose we will use:
Not surprisingly, we have a craft command to create migrations. You can read more about but we'll simplify it down to the command and explain a little bit of what's going on:
This should be fairly straight forward but if you want to learn more, be sure to read the documentation.
We won't go into much more detail here about different types of relationships but to learn more, read the documentation.
Masonite uses Jinja2 templating so if you don't understand this templating, be sure to .
For more information on static files, checkout the documentaton.
Notice that we used Request
here. This is the Request
object. Where did this come from? This is the power and beauty of Masonite and your first introduction to the . The is an extremely powerful implementation as allows you to ask Masonite for an object (in this case Request
) and get that object. This is an important concept to grasp so be sure to read the documentation further.
Read more about the here.
Notice we used the request()
function. This is what Masonite calls which speed up development. We didn't import anything but we are able to use them. This is because Masonite ships with a that adds builtin functions to the project.
Attribute
Options
Example
run_every
Either a singular or plural version of the accepted time units: minutes
, hours
, days
, months
run_every = '1 day'
run_at
The time in military time ("17:00" for 5pm)
run_at = '17:00'
run_every_hour
Boolean on whether to run every hour or not: True
, False
run_every_hour = True
run_every_minute
Boolean on whether to run every minute or not: True
, False
run_every_minute = True
twice_daily
A tuple on the hours of the day the task should run. Also in military time. (1, 13)
will run at 1am and 1pm.
twice_daily = (1, 13)
Value
Casts to (type)
5432
5432 (int)
true
True (bool)
True
True (bool)
false
False (bool)
False
False (bool)
smtp
smtp (string)
assertContains(value)
assertHasAmount(amount)
assertHeaderIs(key, value)
assertNotFound()
assertNotHasAmount(amount)
assertPathIs(value)
assertHasJson(key, value)
assertParameterIs(parameter, value)
isNamed(name)
assertJsonContains(key, value)
assertIsStatus(code)
hasMiddleware(*middleware)
assertCount(number)
assertHasHeader(name)
hasController(name)
contains(value)
ok()
canView()
hasJson(key, value)
count(number)
amount(number)
isGet()
isPost()
isPut()
isPatch()
isDelete()
hasSession(key, value)
parameterIs()
headerIs()
Method
Description
Example
.line()
Creates a single line of text like text you would see in a paragraph tag
line('this is a line of text')
.action()
This creates a clickable looking button. The kwargs include href
and style
. The styles are bootstraps button styles to include default
, success
, danger
, info
etc.
action('Click Me', href="http://google.com", style="danger")
.view()
This is the normal view object here so you can pass in any templates and dictionary you need.
.view('mail/header', {'key': 'value'})
.panel()
This creates a grey background header panel.
.panel('Some Header')
.heading()
Creates a header
.heading('Welcome!')
.subject()
The subject of the email
.subject('New Account!')
.dry()
Sets all the necessary fields but does not actually send the email. This is great for testing purposes. This takes no parameters
.dry()
.driver()
The driver you want to use to send the email
.driver('mailgun')
Method
Description
Example
.token()
This is your Slack token that has the correct permission scopes.
.token('xoxp-359926262626-35...')
.text()
The text you want to show in the message
.text('Welcome to Masonite!')
.channel()
The channel you want to broadcast to. If the value you supply starts with a # sign then Notifications will make a POST request with your token to the Slack channel list API and get the channel ID. You can specify the channel ID directly if you don't want this behavior
.channel('#general') .channel('CHSUU862')
.as_user()
The username you want to show as the message
.as_user('Masonite Bot')
.icon()
The icon you want to show. This needs to be a Slack emoji
.icon(':fire:')
.as_current_user()
This sets a boolean value to True on whether the message should show as the currently authenticated user.
.as_current_user()
.without_markdown()
This will not parse any markdown in the message. This is a boolean value and requires no parameters.
.without_markdown()
.dont_unfurl()
This sets a boolean to False on whether the message should show any attachments. Usually slack will show an icon of the website when posting a link. This disables that feature for the current message.
.dont_unfurl()
.as_snippet()
Used to post the current message as a snippet instead of as a normal message. This option has 3 keyword arguments. The file_type
, name
, and title
. This uses a different API endpoint so some previous methods may not be used.
.as_snippet(file_type='python', name='snippet', title='Awesome Snippet')
.comment()
Only used when using the .as_snippet() method. This will set a comment on the snippet.
.comment('Great Snippet')
.button()
Used to create action buttons under a message. This requires text
and a url
but can also contain style
, and confirm
.button('Sure!', 'http://google.com', style='primary', confirm='Are you sure?')
.dry()
Sets all the necessary fields but does not actually send the email. This is great for testing purposes. This takes no parameters
.dry()
username | string | The username to authenticate using your authentication model |
password | string | The password to authenticate using your authentication model |
token | string | The expired JWT token |
Masonite can handle most of the application that will be built with it. Python is clearly not the fastest language so if it is brute speed to manage 100,000 simultaneous connections than no application written in Python will suit your needs. Those types of application will typically require some Node.js server and asynchronous tasks on multiple servers but if you are going with a Python framework than it is likely you will not need that much juice.
Out of the box, Masonite is designed to run as a great development environment. There are a few trade off's that need to be made in order to stably run Masonite on all operating systems. Masonite defaults to running as the WSGI server with Waitress. This was chosen because waitress is a WSGI server that runs on both Windows and Mac and is powerful enough to run most application during development.
As your application is entering the deployment phase you should look away from running the server with Waitress and look to more production ready options such as uWSGI and Gunicorn. Gunicorn has been tested to run faster than uWSGI both using their default options but uWSGI may be able to be tweaked in a way that is faster dependent on your application.
Because Masonite is simply a WSGI application, any deployment tutorials you research should simply be how to setup a WSGI application on the platform you are trying to deploy to.
So remember, Waitress is great for development but a better WSGI server like Gunicorn should be used when deploying to production.