Skip to content

Commit

Permalink
V2 - Release 2.0.4 (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsil authored Aug 7, 2023
2 parents 833073e + f7eb29b commit 49a0e37
Show file tree
Hide file tree
Showing 43 changed files with 1,478 additions and 158 deletions.
3 changes: 3 additions & 0 deletions .pdbrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

alias kkk os.system('kill -9 %d' % os.getpid())
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ use it.
* **Pluggables**: Create plugins for Esmerald and hook them into any application and/or
distribute them.
* **DAO and AsyncDAO**: Avoid database calls directly from the APIs. Use business objects instead.
* **Saffier ORM**: Native support for [Saffier ORM](./databases/saffier/motivation.md).
* **Saffier ORM**: Native support for [Saffier ORM](https://esmerald.dev/databases/saffier/motivation.md).
* **Edgy**: Native support for [Edgy](https://esmerald.dev/databases/edgy/motivation.md).
* **APIView**: Class Based endpoints for your beloved OOP design.
* **JSON serialization/deserialization**: Both UJSON and ORJON support.
* **Lifespan**: Support for the newly lifespan and on_start/on_shutdown events.
Expand Down
128 changes: 128 additions & 0 deletions docs/databases/edgy/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Example

Since [Edgy](https://edgy.tarsild.io) if from the same author of Esmerald, it gives some
extra motivation for its use and therefore an example in how to use the
[JWTAuthMiddleware](./middleware.md), even if in a very simplistic, within your Esmerald application.


Let us build a simple integration and application where we will be creating:

- [Create user model](#create-user-model) by using the provided default from Esmerald.
- [Create user API](#create-user-api) to create a user in the system.
- [Login API](#login-api) to authenticate the user.
- [Home API](#home-api) to authenticate the user and return the logged-in user email.
- [Assemble the apis](#assemble-the-apis) where we wrap the application.

We will be using SQLite for this example but feel free to integrate with your database.

We will also be assuming the following:

- Models are inside an `accounts/models.py`
- Views/APIs are inside an `accounts/views.py`
- The main application is inside an `app.py`
- The [jwt_config](../../configurations/jwt.md#jwtconfig-and-application-settings)
is inside your global [settings](../../application/settings.md).

**Lets go!**

## Create user model

First, we need to create a model that will be storing the users in the system. We will be
defaulting to the one model provided by Esmerald out-of-the-box.

```python title="accounts/models.py"
{!> ../docs_src/databases/edgy/example/create_model.py !}
```

## Create user API

Now that the [user model](#create-user-model) is defined and created, time to create an api
that allows the creation of users in the system.

This example won't cover corner cases like integrity in ase of duplicates and so on as this is
something that you can easily manage.

```python title="accounts/views.py"
{!> ../docs_src/databases/edgy/example/create_user.py !}
```

## Login API

Now the [create user](#create-user-api) is available to us to be used later on, we need a view
that also allow us to login and return the JWT access token.

For this API to work, we need to garantee the data being sent is valid, authenticate and then
return the JWT token.

```python title="accounts/views.py"
{!> ../docs_src/databases/edgy/example/login.py !}
```

Ooof! There is a lot going on here right? Well, yes but this is also intentional. The `login`
is actually very simple, it just receives a payload and throws that payload into validation
inside the `BackendAuthentication`.

For those familiar with similar objects, like Django backends, this `BackendAuthentication` does
roughly the same thing and it is quite robust since it is using pydantic when creating the instance
which takes advantage of the validations automatically for you.

The `BackendAuthentication` once created inside the `login` and validated with the given fields,
simply proceeds with the `authenticate` method where it will return the JWT for the user.

!!! Warning
As mentioned before in the assumptions on the top of the document, it was assumed you put your
[jwt_config](../../configurations/jwt.md#jwtconfig-and-application-settings) inside your global settings.

## Home API

Now it is time to create the api that will be returning the email of the logged in user when hit.
The API is pretty much simple and clean.

```python title="accounts/views.py"
{!> ../docs_src/databases/edgy/example/home.py !}
```

## Assemble the APIs

Now it the time where we assemble everything in one place and create our Esmerald application.

```python title="app.py"
{!> ../docs_src/databases/edgy/example/assemble.py !}
```

Did you notice the import of the `JWTAuthMiddleware` is inside the
[Include](../../routing/routes.md#include) and not in the main Esmerald instance?

**It is intentional!** Each include handles its own middlewares and to create a user and login
you **don't want to be logged-in** and for that reason, the `JWTAuthMiddleware` is only for those
endpoints that **require authentication**.

Now this assembling is actually very clean, right? Yes and the reason for that is because Esmerald
itself promotes clean design.

We have imported all the APIs directly in the `app.py` but this **is not mandatory**. You can
take advantage of the [Include](../../routing/routes.md#include) and clean your application
even more.

## Extra

Come on, give it a try, create your own version and then try to access the `home`.

Let us see how we could access `/` using the current setup.

For this will be using `httpx` but you are free to use whatever client you prefer.

### Steps

1. Create a user.
2. Login and get the jwt token.
3. Access the home `/`.

```python
{!> ../docs_src/databases/edgy/example/access.py !}
```

## Conclusions

This is just a simple example how you could use Edgy with the provided `JWTAuthMiddleware`
from **Esmerald** and build a quick, yet robust, login system and access protected APIs.
60 changes: 60 additions & 0 deletions docs/databases/edgy/middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Middleware

As part of the support, Esmerald developed an authentication middleware using python-jose allowing JWT integration
with the current [supported models](./models.md#user).

## JWTAuthMiddleware

This simple but effective middleware extends the [BaseAuthMiddleware](../../middleware/middleware.md#baseauthmiddleware)
and enables the authentication via JWT.

```python
from esmerald.contrib.auth.edgy.middleware import JWTAuthMiddleware
```

### Parameters

* `app` - Any ASGI app instance. E.g.: Esmerald instance.
* `config` - An instance of [JWTConfig](../../configurations/jwt.md) object.
* `user` - The user class (not instance!) being used by the application.

## How to use it

There are different ways of calling this middleware in any Esmerald application.

### Via settings

```python
{!> ../docs_src/configurations/jwt/settings.py!}
```

### Via application instantiation

```python
{!> ../docs_src/databases/edgy/middleware/example1.py !}
```

### Via overriding the JWTAuthMiddleware

=== "Via app instance"

```python
{!> ../docs_src/databases/edgy/middleware/example2.py !}
```

=== "Via app settings"

```python
{!> ../docs_src/databases/edgy/middleware/example3.py !}
```

### Important note

In the examples you could see sometimes the `StarletteMiddleware` being used and in other you didn't. The reason behind
is very simple and also explained in the [middleware section](../../middleware/middleware.md#important).

If you need to specify parameters in your middleware then you will need to wrap it in a `starlette.middleware.Middleware`
object to do it so.

If no parameters are needed, then you can simply pass the middleware class directly and Esmerald will take care of the
rest.
162 changes: 162 additions & 0 deletions docs/databases/edgy/models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Models

In simple terms, models are a representation of a database table in the format of an object declared by the language
implementing.

## User models

Integrating with Edgy, Esmerald already provides some of the models that helps you with the
initial configuration.

1. `AbstractUser` - The base user class containing all the fields required a user.
2. `User` - A subsclass of `AbstractUser`

## User

Extenting the existing `User` model is as simple as this:

```python hl_lines="17 32"
{!> ../docs_src/databases/edgy/models.py !}
```

This is a clean way of declaring the models and using the Edgy docs, you can easily understand
why this is like the way it is.

### Meta class

There are wayf of making the models and the registry cleaner, after all, you might want to use the
same registry in different models across multiple applications in your codebase.

One way and a way Esmerald always recommend, is by leveraging the [settings](../../application/settings.md)

### Leveraging the settings for your models

Let us use the same example but this time, we will be using the settings.
Since **you can access the settings anywhere in the codebase**.

Check it out the example below and how by using the settings, you can literally leverage Esmerald
with Edgy.

=== "settings.py"

```python hl_lines="10-12"
{!> ../docs_src/databases/edgy/settings/settings.py !}
```

=== "models.py"

```python hl_lines="17 32"
{!> ../docs_src/databases/edgy/settings/models.py !}
```

You simply isolated your common database connection and registry inside the globally accessible
settings and with that you can import in any Esmerald application, ChildEsmerald or whatever you
prefer without the need of repeating yourself.

### User model fields

If you are familiar with Django then you are also aware of the way they have their users table and the way they
have the fields declared. Esmerald has a similar approach and provides the following.

* `first_name`
* `last_name`
* `username`
* `email`
* `password`
* `last_login`
* `is_active`
* `is_staff`
* `is_superuser`

### The functions available

Using simply this model it does not bring too much benefits as it si something you can do easily and fast but the
functionality applied to this model is already something that can be something that requires some extra time to
assemble.

!!! Warning
The following examples assume that you are taking advantage of the settings as
[decribed before](#leveraging-the-settings-for-your-models).

**create_user**

```python
{!> ../docs_src/databases/edgy/create_user.py !}
```

**create_superuser**

```python
{!> ../docs_src/databases/edgy/create_superuser.py !}
```

**check_password**

```python hl_lines="28"
{!> ../docs_src/databases/edgy/check_password.py !}
```

Because you are using the `User` provided by Esmerald, the same object is also prepared to validate
the password against the system. If you are familiar with Django, this was based on it and has the
same principle.

**set_password**

```python hl_lines="28"
{!> ../docs_src/databases/edgy/set_password.py !}
```

The same for setting passwords. The `User` already contains the functionality to set a password of
a given `User` instance.

### What happened

Although the way of using the `User` table was intentionally designed to be simple there is in fact a lot going
on behind the scenes.

When using the `create_user` and `create_superuser` behind the scenes it is not only creating that same record and
storing in the database but is also <a href='https://nordpass.com/blog/password-hash/' target='_blank'>hashing</a>
the password for you using the built-in Esmerald [password hashers](#password-hashers) and this is a life saving
time and implementation.

Esmerald also provides the `set_password` and `check_password` functions to make it easier to
validate and change a user's password using the `User` instance.

## Password Hashers

Esmerald already brings some pre-defined password hashers that are available in the
[Esmerald settings](../../application/settings.md) and ready to be used.

```python

@property
def password_hashers(self) -> List[str]:
return [
"esmerald.contrib.auth.hashers.PBKDF2PasswordHasher",
"esmerald.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
]

```

Esmerald uses <a href='https://passlib.readthedocs.io/en/stable/' target='_blank'>passlib</a> under the hood
in order to facilitate the process of hashing passwords.

You can always override the property `password_hashers` in your
[custom settings](../../application/settings.md#custom-settings) and use your own.

```python
{!> ../docs_src/databases/edgy/hashers.py !}
```

## Migrations

You can use any migration tool as you see fit. It is recommended
<a href='https://alembic.sqlalchemy.org/en/latest/' target='_blank'>Alembic</a>.

Edgy also provides some insights in
[how to migrate using alembic](https://edgy.tarsild.io/migrations/migrations).

## General example

More examples and more thorough explanations how to use [Edgy](https://edgy.tarsild.io)
can be consulted in their [documentation](https://edgy.tarsild.io).
Loading

0 comments on commit 49a0e37

Please sign in to comment.