Skip to content

Commit

Permalink
Merge pull request #5 from py-package/feature/finder
Browse files Browse the repository at this point in the history
Feature/finder
  • Loading branch information
yubarajshrestha committed Jul 17, 2022
2 parents 68287e2 + 7619a1a commit 76cf314
Show file tree
Hide file tree
Showing 18 changed files with 346 additions and 88 deletions.
139 changes: 97 additions & 42 deletions README.md
Expand Up @@ -57,58 +57,19 @@ Then you can publish the package resources (if needed) by doing:
python craft package:publish multitenancy
```

### Usage
### Command Usage

You'll get bunch of commands to manage tenants.

**Create a new tenant**

This will prompt few questions just provider answers and that's it.

```bash
python craft tenancy:create
```

> Note: After creating a new tenant, you will need to setup related database configuration in `config/multitenancy.py`.
For example, if your tenant database name is `tenant1`, then you need to add the following to `config/multitenancy.py`:

```python
# config/multitenancy.py

TENANTS = {
"tenant1": {
"driver": "sqlite",
"database": env("SQLITE_DB_DATABASE", "tenant1.sqlite3"),
"prefix": "",
"log_queries": env("DB_LOG"),
},
}
```

You can use any database driver that Masonite supports. For example, if you want to use MySQL, then you can use the following:

```python
# config/multitenancy.py

TENANTS = {
"tenant1": {
"driver": "mysql",
"host": env("DB_HOST"),
"user": env("DB_USERNAME"),
"password": env("DB_PASSWORD"),
"database": env("DB_DATABASE"),
"port": env("DB_PORT"),
"prefix": "",
"grammar": "mysql",
"options": {
"charset": "utf8mb4",
},
"log_queries": env("DB_LOG"),
},
}
```

> Note: Make sure you have set the `multitenancy` configuration before running any tenant related commands.
This will also automatically generate new database connection based on your `default` database connection from `config/database.py`.

**List all tenants**

Expand Down Expand Up @@ -162,6 +123,96 @@ python craft tenancy:seed --tenants=tenant1,tenant2
python craft tenancy:seed
```

### Using Tenancy Facade

**Create a new tenant**

```python
from multitenancy.facades import Tenancy

# creates a new tenant and returns instance of new Tenant
Tenancy.create(
name='tenant1',
domain='tenant1.example.com',
database='tenant1',
)
```

**Get tenant**

```python
from multitenancy.facades import Tenancy

# by id
Tenancy.get_tenant_by_id(1)

# by domain
Tenancy.get_tenant_by_domain('tenant1.example.com')

# by database name
Tenancy.get_tenant_by_database('tenant1')
```

**Delete tenant**

```python
from multitenancy.facades import Tenancy


tenant = Tenant.find(1)
Tenancy.delete(tenant)
```

**Connections**

```python
from multitenancy.facades import Tenancy

# setting tenant specific connection
tenant = Tenant.find(1)
Tenancy.set_connection(tenant)

# resetting to default connection
Tenancy.reset_connection()
```

Event though above approach can be used to set tenant specific connection, and do tenant related tasks, it's recommended to use `TenantContext` instead.

### Using Tenant Context

You might sometime need to get data from different tenant in your application or you might have to do some logic based on tenant. In this case you can use `TenantContext` class to get tenant data.

```python
from multitenancy.contexts import TenantContext
from multitenancy.models.Tenant import Tenant

tenant = Tenant.where('name', '=', 'tenant1').first()

with TenantContext(tenant=tenant):
# do something with tenant1 data
# ...
```

You can also do all other tenant specific tasks like: `migrations`, `seeds`.

```python
from multitenancy.contexts import TenantContext
from multitenancy.models.Tenant import Tenant

tenant = Tenant.where('name', '=', 'tenant1').first()

with TenantContext(tenant=tenant) as ctx:
# migrate the database
ctx.migrate()
ctx.migrate_refresh()
ctx.migrate_rollback()
ctx.migrate_reset()
ctx.migrate_status()

# seed the database
ctx.seed()
```

### Final Step

Now the multitenancy is almost ready to use. The final step is to make use of tenancy middleware. This middleware will be used to specify tenant in request on the fly. So, basically you have to attach this middleware to all the routes that are tenant aware.
Expand All @@ -177,6 +228,10 @@ Route.get("/tenant-aware-routes", "WelcomeController@show").middleware("multiten
In above example, `/tenant-aware-routes` will be tenant aware. It means that if you have tenant setup and you are trying to access `/tenant-aware-routes` then you will get tenant specific items from the database.


### TODO

- [x] Different database server for each tenant

### Contributing

Please read the [Contributing Documentation](CONTRIBUTING.md) here.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -8,7 +8,7 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version="0.0.4",
version="0.0.5",
packages=[
"multitenancy",
"multitenancy.commands",
Expand Down
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyDelete.py
@@ -1,4 +1,5 @@
from masonite.commands import Command
from ..facades import Tenancy


class TenancyDelete(Command):
Expand All @@ -12,17 +13,16 @@ class TenancyDelete(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def get_tenants(self):
"""Returns a list of all tenants."""
tenants = self.option("tenants")
try:
if tenants == "default":
tenants = self.tenancy.get_tenants()
tenants = Tenancy.get_tenants()
else:
tenants = tenants.split(",")
tenants = [self.tenancy.get_tenant(tenant) for tenant in tenants]
tenants = [Tenancy.get_tenant(tenant) for tenant in tenants]
return tenants
except Exception as e:
self.error(e)
Expand All @@ -36,6 +36,6 @@ def handle(self):
exit()

for tenant in tenants:
self.app.make("multitenancy").delete(tenant)
Tenancy.delete(tenant)

self.info("All tenants deleted!")
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyMigrate.py
@@ -1,6 +1,7 @@
import os
from masonite.commands.Command import Command
from masoniteorm.migrations import Migration
from ..facades import Tenancy


class TenancyMigrate(Command):
Expand All @@ -18,14 +19,13 @@ class TenancyMigrate(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def migration(self, tenant):
self.tenancy.setup_connection(tenant)
Tenancy.set_connection(tenant)

migration = Migration(
command_class=self,
connection=tenant.database,
connection="default",
migration_directory=self.option("directory"),
config_path=None,
schema=None,
Expand All @@ -47,7 +47,7 @@ def handle(self):
self.info("Migrations cancelled")
exit(0)

tenants = self.tenancy.get_tenants(self.option("tenants"))
tenants = Tenancy.get_tenants(self.option("tenants"))

if len(tenants) == 0:
self.error("No tenants found!")
Expand Down
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyMigrateRefresh.py
@@ -1,5 +1,6 @@
from masonite.commands.Command import Command
from masoniteorm.migrations import Migration
from ..facades import Tenancy


class TenancyMigrateRefresh(Command):
Expand All @@ -15,21 +16,20 @@ class TenancyMigrateRefresh(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def migration(self, tenant):
self.tenancy.setup_connection(tenant)
Tenancy.set_connection(tenant)

return Migration(
command_class=self,
connection=tenant.database,
connection="default",
migration_directory=self.option("directory"),
config_path=None,
schema=None,
)

def handle(self):
tenants = self.tenancy.get_tenants(self.option("tenants"))
tenants = Tenancy.get_tenants(self.option("tenants"))

if len(tenants) == 0:
self.error("No tenants found!")
Expand Down
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyMigrateReset.py
@@ -1,5 +1,6 @@
from masonite.commands.Command import Command
from masoniteorm.migrations import Migration
from ..facades import Tenancy


class TenancyMigrateReset(Command):
Expand All @@ -15,21 +16,20 @@ class TenancyMigrateReset(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def migration(self, tenant):
self.tenancy.setup_connection(tenant)
Tenancy.set_connection(tenant)

return Migration(
command_class=self,
connection=tenant.database,
connection="default",
migration_directory=self.option("directory"),
config_path=None,
schema=None,
)

def handle(self):
tenants = self.tenancy.get_tenants(self.option("tenants"))
tenants = Tenancy.get_tenants(self.option("tenants"))

if len(tenants) == 0:
self.error("No tenants found!")
Expand Down
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyMigrateRollback.py
@@ -1,5 +1,6 @@
from masonite.commands.Command import Command
from masoniteorm.migrations import Migration
from ..facades import Tenancy


class TenancyMigrateRollback(Command):
Expand All @@ -16,21 +17,20 @@ class TenancyMigrateRollback(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def migration(self, tenant):
self.tenancy.setup_connection(tenant)
Tenancy.set_connection(tenant)

return Migration(
command_class=self,
connection=tenant.database,
connection="default",
migration_directory=self.option("directory"),
config_path=None,
schema=None,
)

def handle(self):
tenants = self.tenancy.get_tenants(self.option("tenants"))
tenants = Tenancy.get_tenants(self.option("tenants"))

if len(tenants) == 0:
self.error("No tenants found!")
Expand Down
8 changes: 4 additions & 4 deletions src/multitenancy/commands/TenancyMigrateStatus.py
@@ -1,5 +1,6 @@
from masonite.commands.Command import Command
from masoniteorm.migrations import Migration
from ..facades import Tenancy


class TenancyMigrateStatus(Command):
Expand All @@ -14,14 +15,13 @@ class TenancyMigrateStatus(Command):
def __init__(self, application):
super().__init__()
self.app = application
self.tenancy = self.app.make("multitenancy")

def migration(self, tenant):
self.tenancy.setup_connection(tenant)
Tenancy.set_connection(tenant)

migration = Migration(
command_class=self,
connection=tenant.database,
connection="default",
migration_directory=self.option("directory"),
config_path=None,
schema=None,
Expand All @@ -42,7 +42,7 @@ def migration(self, tenant):
table.render(self.io)

def handle(self):
tenants = self.tenancy.get_tenants(self.option("tenants"))
tenants = Tenancy.get_tenants(self.option("tenants"))

if len(tenants) == 0:
self.error("No tenants found!")
Expand Down

0 comments on commit 76cf314

Please sign in to comment.