├── .deepsource.toml
├── .env-example
├── .envrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── pythonapp.yml
│ └── pythonpublish.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .pypirc
├── .tool-versions
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── TODO.md
├── app
└── observers
│ └── UserObserver.py
├── cc.py
├── conda
├── conda_build_config.yaml
└── meta.yaml
├── config
└── test-database.py
├── databases
├── migrations
│ ├── 2018_01_09_043202_create_users_table.py
│ ├── 2020_04_17_000000_create_friends_table.py
│ ├── 2020_04_17_00000_create_articles_table.py
│ ├── 2020_10_20_152904_create_table_schema_migration.py
│ └── __init__.py
└── seeds
│ ├── database_seeder.py
│ └── user_table_seeder.py
├── makefile
├── orm
├── orm.sqlite3
├── pyproject.toml
├── pytest.ini
├── requirements.dev
├── requirements.txt
├── setup.py
├── src
└── masoniteorm
│ ├── .gitignore
│ ├── __init__.py
│ ├── collection
│ ├── Collection.py
│ └── __init__.py
│ ├── commands
│ ├── CanOverrideConfig.py
│ ├── CanOverrideOptionsDefault.py
│ ├── Command.py
│ ├── Entry.py
│ ├── MakeMigrationCommand.py
│ ├── MakeModelCommand.py
│ ├── MakeModelDocstringCommand.py
│ ├── MakeObserverCommand.py
│ ├── MakeSeedCommand.py
│ ├── MigrateCommand.py
│ ├── MigrateFreshCommand.py
│ ├── MigrateRefreshCommand.py
│ ├── MigrateResetCommand.py
│ ├── MigrateRollbackCommand.py
│ ├── MigrateStatusCommand.py
│ ├── SeedRunCommand.py
│ ├── ShellCommand.py
│ ├── __init__.py
│ └── stubs
│ │ ├── create_migration.stub
│ │ ├── create_seed.stub
│ │ ├── model.stub
│ │ ├── observer.stub
│ │ └── table_migration.stub
│ ├── config.py
│ ├── connections
│ ├── .gitignore
│ ├── BaseConnection.py
│ ├── ConnectionFactory.py
│ ├── ConnectionResolver.py
│ ├── MSSQLConnection.py
│ ├── MySQLConnection.py
│ ├── PostgresConnection.py
│ ├── SQLiteConnection.py
│ └── __init__.py
│ ├── exceptions.py
│ ├── expressions
│ ├── __init__.py
│ └── expressions.py
│ ├── factories
│ ├── Factory.py
│ └── __init__.py
│ ├── helpers
│ ├── __init__.py
│ └── misc.py
│ ├── migrations
│ ├── Migration.py
│ └── __init__.py
│ ├── models
│ ├── MigrationModel.py
│ ├── Model.py
│ ├── Model.pyi
│ ├── Pivot.py
│ └── __init__.py
│ ├── observers
│ ├── ObservesEvents.py
│ └── __init__.py
│ ├── pagination
│ ├── BasePaginator.py
│ ├── LengthAwarePaginator.py
│ ├── SimplePaginator.py
│ └── __init__.py
│ ├── providers
│ ├── ORMProvider.py
│ └── __init__.py
│ ├── query
│ ├── EagerRelation.py
│ ├── QueryBuilder.py
│ ├── __init__.py
│ ├── grammars
│ │ ├── BaseGrammar.py
│ │ ├── MSSQLGrammar.py
│ │ ├── MySQLGrammar.py
│ │ ├── PostgresGrammar.py
│ │ ├── SQLiteGrammar.py
│ │ └── __init__.py
│ └── processors
│ │ ├── MSSQLPostProcessor.py
│ │ ├── MySQLPostProcessor.py
│ │ ├── PostgresPostProcessor.py
│ │ ├── SQLitePostProcessor.py
│ │ └── __init__.py
│ ├── relationships
│ ├── BaseRelationship.py
│ ├── BelongsTo.py
│ ├── BelongsToMany.py
│ ├── HasMany.py
│ ├── HasManyThrough.py
│ ├── HasOne.py
│ ├── HasOneThrough.py
│ ├── MorphMany.py
│ ├── MorphOne.py
│ ├── MorphTo.py
│ ├── MorphToMany.py
│ └── __init__.py
│ ├── schema
│ ├── Blueprint.py
│ ├── Column.py
│ ├── ColumnDiff.py
│ ├── Constraint.py
│ ├── ForeignKeyConstraint.py
│ ├── Index.py
│ ├── Schema.py
│ ├── Table.py
│ ├── TableDiff.py
│ ├── __init__.py
│ └── platforms
│ │ ├── MSSQLPlatform.py
│ │ ├── MySQLPlatform.py
│ │ ├── Platform.py
│ │ ├── PostgresPlatform.py
│ │ ├── SQLitePlatform.py
│ │ └── __init__.py
│ ├── scopes
│ ├── BaseScope.py
│ ├── SoftDeleteScope.py
│ ├── SoftDeletesMixin.py
│ ├── TimeStampsMixin.py
│ ├── TimeStampsScope.py
│ ├── UUIDPrimaryKeyMixin.py
│ ├── UUIDPrimaryKeyScope.py
│ ├── __init__.py
│ └── scope.py
│ ├── seeds
│ ├── Seeder.py
│ └── __init__.py
│ ├── stubs
│ ├── create-migration.html
│ └── table-migration.html
│ └── testing
│ ├── BaseTestCaseSelectGrammar.py
│ └── __init__.py
└── tests
├── User.py
├── collection
└── test_collection.py
├── commands
└── test_shell.py
├── config
└── test_db_url.py
├── connections
└── test_base_connections.py
├── eagers
└── test_eager.py
├── factories
└── test_factories.py
├── integrations
└── config
│ ├── __init__.py
│ └── database.py
├── models
└── test_models.py
├── mssql
├── builder
│ ├── test_mssql_query_builder.py
│ └── test_mssql_query_builder_relationships.py
├── grammar
│ ├── test_mssql_delete_grammar.py
│ ├── test_mssql_insert_grammar.py
│ ├── test_mssql_qmark.py
│ ├── test_mssql_select_grammar.py
│ └── test_mssql_update_grammar.py
└── schema
│ ├── test_mssql_schema_builder.py
│ └── test_mssql_schema_builder_alter.py
├── mysql
├── builder
│ ├── test_mysql_builder_transaction.py
│ ├── test_query_builder.py
│ ├── test_query_builder_scopes.py
│ └── test_transactions.py
├── connections
│ └── test_mysql_connection_selects.py
├── grammar
│ ├── test_mysql_delete_grammar.py
│ ├── test_mysql_insert_grammar.py
│ ├── test_mysql_qmark.py
│ ├── test_mysql_select_grammar.py
│ └── test_mysql_update_grammar.py
├── model
│ ├── test_accessors_and_mutators.py
│ └── test_model.py
├── relationships
│ ├── test_belongs_to_many.py
│ ├── test_has_many_through.py
│ ├── test_has_one_through.py
│ └── test_relationships.py
├── schema
│ ├── test_mysql_schema_builder.py
│ └── test_mysql_schema_builder_alter.py
└── scopes
│ ├── test_can_use_global_scopes.py
│ ├── test_can_use_scopes.py
│ └── test_soft_delete.py
├── postgres
├── builder
│ ├── test_postgres_query_builder.py
│ └── test_postgres_transaction.py
├── grammar
│ ├── test_delete_grammar.py
│ ├── test_insert_grammar.py
│ ├── test_select_grammar.py
│ └── test_update_grammar.py
├── relationships
│ └── test_postgres_relationships.py
└── schema
│ ├── test_postgres_schema_builder.py
│ └── test_postgres_schema_builder_alter.py
├── scopes
└── test_default_global_scopes.py
├── seeds
└── test_seeds.py
├── sqlite
├── builder
│ ├── test_sqlite_builder_insert.py
│ ├── test_sqlite_builder_pagination.py
│ ├── test_sqlite_query_builder.py
│ ├── test_sqlite_query_builder_eager_loading.py
│ ├── test_sqlite_query_builder_relationships.py
│ └── test_sqlite_transaction.py
├── grammar
│ ├── test_sqlite_delete_grammar.py
│ ├── test_sqlite_insert_grammar.py
│ ├── test_sqlite_select_grammar.py
│ └── test_sqlite_update_grammar.py
├── models
│ ├── test_attach_detach.py
│ ├── test_observers.py
│ └── test_sqlite_model.py
├── relationships
│ ├── test_sqlite_has_many_through_relationship.py
│ ├── test_sqlite_has_one_through_relationship.py
│ ├── test_sqlite_polymorphic.py
│ └── test_sqlite_relationships.py
└── schema
│ ├── test_sqlite_schema_builder.py
│ ├── test_sqlite_schema_builder_alter.py
│ ├── test_table.py
│ └── test_table_diff.py
└── utils.py
/.deepsource.toml:
--------------------------------------------------------------------------------
1 | # generated by deepsource.io
2 | version = 1
3 |
4 | test_patterns = [
5 | 'tests/**/*.py'
6 | ]
7 |
8 | exclude_patterns = [
9 | 'databases/migrations/*'
10 | ]
11 |
12 | [[analyzers]]
13 | name = "python"
14 | enabled = true
15 | runtime_version = "3.x.x"
16 |
--------------------------------------------------------------------------------
/.env-example:
--------------------------------------------------------------------------------
1 |
2 | RUN_MYSQL_DATABASE=False
3 |
4 | MYSQL_DATABASE_HOST=
5 | MYSQL_DATABASE_USER=
6 | MYSQL_DATABASE_PASSWORD=
7 | MYSQL_DATABASE_DATABASE=
8 | MYSQL_DATABASE_PORT=
9 |
10 | POSTGRES_DATABASE_HOST=
11 | POSTGRES_DATABASE_USER=
12 | POSTGRES_DATABASE_PASSWORD=
13 | POSTGRES_DATABASE_DATABASE=
14 | POSTGRES_DATABASE_PORT=
15 |
16 | DATABASE_URL=
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use asdf
2 | layout python
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: A bug would be defined as an issue / problem in the original requirement. If the feature works but could be enhanced please use the feature request option.
4 | title: ''
5 | labels: 'bug'
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 | 4. See error
18 |
19 | **Expected behavior**
20 | What do you believe should be happening?
21 |
22 | **Screenshots or code snippets**
23 | Screenshots help a lot. If applicable, add screenshots to help explain your problem.
24 |
25 | **Desktop (please complete the following information):**
26 | - OS: [e.g. Mac OSX, Windows]
27 | - Version [e.g. Big Sur, 10]
28 |
29 | **What database are you using?**
30 | - Type: [e.g. Postgres, MySQL, SQLite]
31 | - Version [e.g. 8, 9.1, 10.5]
32 | - Masonite ORM [e.g. v1.0.26, v1.0.27]
33 |
34 | **Additional context**
35 | Any other steps you are doing or any other related information that will help us debug the problem please put here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request or enhancement
3 | about: Suggest an idea or improvement for this project.
4 | title: ''
5 | labels: enhancement, feature request
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the feature as you'd like to see it**
11 | A clear and concise description of what you want to happen.
12 |
13 | **What do we currently have to do now?**
14 | Give some examples or code snippets on the current way of doing things.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
19 | - [ ] Is this a breaking change?
20 |
--------------------------------------------------------------------------------
/.github/workflows/pythonapp.yml:
--------------------------------------------------------------------------------
1 | name: Test Application
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-20.04
8 |
9 | services:
10 | postgres:
11 | image: postgres:10.8
12 | env:
13 | POSTGRES_USER: postgres
14 | POSTGRES_PASSWORD: postgres
15 | POSTGRES_DB: postgres
16 | ports:
17 | # will assign a random free host port
18 | - 5432/tcp
19 | # needed because the postgres container does not provide a healthcheck
20 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
21 |
22 | mysql:
23 | image: mysql:5.7
24 | env:
25 | MYSQL_ALLOW_EMPTY_PASSWORD: yes
26 | MYSQL_DATABASE: orm
27 | ports:
28 | - 3306
29 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
30 |
31 | strategy:
32 | matrix:
33 | python-version: ["3.6", "3.7", "3.8", "3.9"]
34 | name: Python ${{ matrix.python-version }}
35 | steps:
36 | - uses: actions/checkout@v1
37 | - name: Set up Python ${{ matrix.python-version }}
38 | uses: actions/setup-python@v4
39 | with:
40 | python-version: ${{ matrix.python-version }}
41 | - name: Install dependencies
42 | run: |
43 | make init-ci
44 | - name: Test with pytest
45 | env:
46 | POSTGRES_DATABASE_HOST: localhost
47 | POSTGRES_DATABASE_DATABASE: postgres
48 | POSTGRES_DATABASE_USER: postgres
49 | POSTGRES_DATABASE_PASSWORD: postgres
50 | POSTGRES_DATABASE_PORT: ${{ job.services.postgres.ports[5432] }}
51 | MYSQL_DATABASE_HOST: localhost
52 | MYSQL_DATABASE_DATABASE: orm
53 | MYSQL_DATABASE_USER: root
54 | MYSQL_DATABASE_PORT: ${{ job.services.mysql.ports[3306] }}
55 | DB_CONFIG_PATH: tests/integrations/config/database.py
56 | run: |
57 | python orm migrate --connection postgres
58 | python orm migrate --connection mysql
59 | make test
60 | lint:
61 | runs-on: ubuntu-20.04
62 | name: Lint
63 | steps:
64 | - uses: actions/checkout@v1
65 | - name: Set up Python 3.6
66 | uses: actions/setup-python@v4
67 | with:
68 | python-version: 3.6
69 | - name: Install Flake8
70 | run: |
71 | pip install flake8-pyproject
72 | - name: Lint
73 | run: make lint
74 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpublish.yml:
--------------------------------------------------------------------------------
1 | name: Upload Python Package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-20.04
10 |
11 | services:
12 | postgres:
13 | image: postgres:10.8
14 | env:
15 | POSTGRES_USER: postgres
16 | POSTGRES_PASSWORD: postgres
17 | POSTGRES_DB: postgres
18 | ports:
19 | # will assign a random free host port
20 | - 5432/tcp
21 | # needed because the postgres container does not provide a healthcheck
22 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
23 |
24 | mysql:
25 | image: mysql:5.7
26 | env:
27 | MYSQL_ALLOW_EMPTY_PASSWORD: yes
28 | MYSQL_DATABASE: orm
29 | ports:
30 | - 3306
31 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
32 |
33 | strategy:
34 | matrix:
35 | python-version: ["3.6"]
36 | name: Python ${{ matrix.python-version }}
37 | steps:
38 | - uses: actions/checkout@v1
39 | - name: Set up Python ${{ matrix.python-version }}
40 | uses: actions/setup-python@v4
41 | with:
42 | python-version: ${{ matrix.python-version }}
43 | - name: Install dependencies
44 | run: |
45 | make init-ci
46 | - name: Test with Pytest and Publish to PYPI
47 | env:
48 | POSTGRES_DATABASE_HOST: localhost
49 | POSTGRES_DATABASE_DATABASE: postgres
50 | POSTGRES_DATABASE_USER: postgres
51 | POSTGRES_DATABASE_PASSWORD: postgres
52 | POSTGRES_DATABASE_PORT: ${{ job.services.postgres.ports[5432] }}
53 | MYSQL_DATABASE_HOST: localhost
54 | MYSQL_DATABASE_DATABASE: orm
55 | MYSQL_DATABASE_USER: root
56 | MYSQL_DATABASE_PORT: ${{ job.services.mysql.ports[3306] }}
57 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
58 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
59 | DB_CONFIG_PATH: tests/integrations/config/database.py
60 | run: |
61 | python orm migrate --connection postgres
62 | python orm migrate --connection mysql
63 | make publish
64 | - name: Discord notification
65 | env:
66 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
67 | uses: Ilshidur/action-discord@master
68 | with:
69 | args: "{{ EVENT_PAYLOAD.repository.full_name }} {{ EVENT_PAYLOAD.release.tag_name }} has been released. Checkout the full release notes here: {{ EVENT_PAYLOAD.release.html_url }}"
70 |
71 | publish:
72 | runs-on: ubuntu-latest
73 | steps:
74 | - uses: actions/checkout@v2
75 | - name: publish-to-conda
76 | uses: fcakyon/conda-publish-action@v1.3
77 | with:
78 | subdir: "conda"
79 | anacondatoken: ${{ secrets.ANACONDA_TOKEN }}
80 | platforms: "win osx linux"
81 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | venv
2 | .direnv
3 | .python-version
4 | .vscode
5 | .pytest_*
6 | **/*__pycache__*
7 | **/*.DS_Store*
8 | masonite_validation*
9 | dist
10 | .env
11 | *.db
12 | *.sqlite3
13 | .idea
14 | **/*.egg-info
15 | htmlcov/*
16 | coverage.xml
17 | .coverage
18 | *.log
19 | build
20 | /orm.sqlite3
21 | /.bootstrapped-pip
22 | /.ignore-pre-commit
23 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/psf/black
3 | rev: 25.1.0
4 | hooks:
5 | - id: black
6 | exclude: |
7 | (?x)(
8 | ^build|
9 | ^conda
10 | )
11 |
12 | - repo: https://github.com/pycqa/isort
13 | rev: 6.0.1
14 | hooks:
15 | - id: isort
16 | exclude: |
17 | (?x)(
18 | ^build|
19 | ^conda
20 | )
21 |
22 |
23 | - repo: https://github.com/pycqa/flake8
24 | rev: 7.1.2
25 | hooks:
26 | - id: flake8
27 | additional_dependencies: [flake8-pyproject]
28 | exclude: |
29 | (?x)(
30 | ^build|
31 | ^conda
32 | )
33 |
--------------------------------------------------------------------------------
/.pypirc:
--------------------------------------------------------------------------------
1 | [distutils]
2 | index-servers =
3 | pypi
4 | pypitest
5 |
6 | [pypi]
7 | username=username
8 | password=password
9 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | python 3.8.10
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Joseph Mancuso
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # include src/package/some/directory/*
2 | include src/masoniteorm/commands/stubs/*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Masonite ORM
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## Installation & Usage
17 |
18 | All documentation can be found here [https://orm.masoniteproject.com](https://orm.masoniteproject.com).
19 |
20 | Hop on [Masonite Discord Community](https://discord.gg/TwKeFahmPZ) to ask any questions you need!
21 |
22 | ## Contributing
23 |
24 | If you would like to contribute please read the [Contributing Documentation](CONTRIBUTING.md) here.
25 |
26 | ## License
27 |
28 | Masonite ORM is open-sourced software licensed under the [MIT License](LICENSE).
29 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | - [x] fix scopes - need to find a new way to perform scopes
2 |
3 | - [x] scopes need to be set on the model and then passed off to the query builder
4 |
5 | - [x] global scopes
6 | - on select need to call a scope
7 | - on delete need to call a scope
8 | - need to be able to remove global scopes
9 | - need to be able to able to call something like with_trashed()
10 | - this needs to remove global scopes only from the soft deletes class
--------------------------------------------------------------------------------
/app/observers/UserObserver.py:
--------------------------------------------------------------------------------
1 | """User Observer"""
2 |
3 | from masoniteorm.models import Model
4 |
5 |
6 | class UserObserver:
7 | def created(self, clients):
8 | """Handle the Clients "created" event.
9 |
10 | Args:
11 | clients (masoniteorm.models.Model): Clients model.
12 | """
13 | pass
14 |
15 | def creating(self, clients):
16 | """Handle the Clients "creating" event.
17 |
18 | Args:
19 | clients (masoniteorm.models.Model): Clients model.
20 | """
21 | pass
22 |
23 | def saving(self, clients):
24 | """Handle the Clients "saving" event.
25 |
26 | Args:
27 | clients (masoniteorm.models.Model): Clients model.
28 | """
29 | pass
30 |
31 | def saved(self, clients):
32 | """Handle the Clients "saved" event.
33 |
34 | Args:
35 | clients (masoniteorm.models.Model): Clients model.
36 | """
37 | pass
38 |
39 | def updating(self, clients):
40 | """Handle the Clients "updating" event.
41 |
42 | Args:
43 | clients (masoniteorm.models.Model): Clients model.
44 | """
45 | pass
46 |
47 | def updated(self, clients):
48 | """Handle the Clients "updated" event.
49 |
50 | Args:
51 | clients (masoniteorm.models.Model): Clients model.
52 | """
53 | pass
54 |
55 | def booted(self, clients):
56 | """Handle the Clients "booted" event.
57 |
58 | Args:
59 | clients (masoniteorm.models.Model): Clients model.
60 | """
61 | pass
62 |
63 | def booting(self, clients):
64 | """Handle the Clients "booting" event.
65 |
66 | Args:
67 | clients (masoniteorm.models.Model): Clients model.
68 | """
69 | pass
70 |
71 | def hydrating(self, clients):
72 | """Handle the Clients "hydrating" event.
73 |
74 | Args:
75 | clients (masoniteorm.models.Model): Clients model.
76 | """
77 | pass
78 |
79 | def hydrated(self, clients):
80 | """Handle the Clients "hydrated" event.
81 |
82 | Args:
83 | clients (masoniteorm.models.Model): Clients model.
84 | """
85 | pass
86 |
87 | def deleting(self, clients):
88 | """Handle the Clients "deleting" event.
89 |
90 | Args:
91 | clients (masoniteorm.models.Model): Clients model.
92 | """
93 | pass
94 |
95 | def deleted(self, clients):
96 | """Handle the Clients "deleted" event.
97 |
98 | Args:
99 | clients (masoniteorm.models.Model): Clients model.
100 | """
101 | pass
102 |
--------------------------------------------------------------------------------
/cc.py:
--------------------------------------------------------------------------------
1 | """Sandbox experimental file used to quickly feature test features of the package
2 | """
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.connections import MySQLConnection, PostgresConnection
6 | from src.masoniteorm.query.grammars import MySQLGrammar, PostgresGrammar
7 | from src.masoniteorm.models import Model
8 | from src.masoniteorm.relationships import has_many
9 | import inspect
10 |
11 |
12 | # builder = QueryBuilder(connection=PostgresConnection, grammar=PostgresGrammar).table("users").on("postgres")
13 |
14 |
15 |
16 | # print(builder.where("id", 1).or_where(lambda q: q.where('id', 2).or_where('id', 3)).get())
17 |
18 | class User(Model):
19 | __connection__ = "t"
20 | __table__ = "users"
21 | __dates__ = ["verified_at"]
22 |
23 | @has_many("id", "user_id")
24 | def articles(self):
25 | return Article
26 | class Company(Model):
27 | __connection__ = "sqlite"
28 |
29 |
30 | # user = User.create({"name": "phill", "email": "phill"})
31 | # print(inspect.isclass(User))
32 | user = User.first()
33 | # user.update({"verified_at": None, "updated_at": None})
34 | print(user.serialize())
35 |
36 | # print(user.serialize())
37 | # print(User.first())
--------------------------------------------------------------------------------
/conda/conda_build_config.yaml:
--------------------------------------------------------------------------------
1 | python:
2 | - 3.6
3 | - 3.7
4 | - 3.8
5 | - 3.9
6 |
--------------------------------------------------------------------------------
/conda/meta.yaml:
--------------------------------------------------------------------------------
1 | {% set data = load_setup_py_data() %}
2 |
3 | package:
4 | name: masonite-orm
5 | version: {{ data['version'] }}
6 |
7 | source:
8 | path: ..
9 |
10 | build:
11 | number: 0
12 | script: python setup.py install --single-version-externally-managed --record=record.txt
13 |
14 | requirements:
15 | build:
16 | - python
17 |
18 | run:
19 | - python
20 |
21 | test:
22 | run:
23 | - python -m pytest
24 |
25 | about:
26 | home: {{ data['url'] }}
27 | license: {{ data['license'] }}
28 | summary: {{ data['description'] }}
--------------------------------------------------------------------------------
/config/test-database.py:
--------------------------------------------------------------------------------
1 | from src.masoniteorm.connections import ConnectionResolver
2 |
3 | DATABASES = {
4 | "default": "mysql",
5 | "mysql": {
6 | "host": "127.0.0.1",
7 | "driver": "mysql",
8 | "database": "masonite",
9 | "user": "root",
10 | "password": "",
11 | "port": 3306,
12 | "log_queries": False,
13 | "options": {
14 | #
15 | }
16 | },
17 | "postgres": {
18 | "host": "127.0.0.1",
19 | "driver": "postgres",
20 | "database": "masonite",
21 | "user": "root",
22 | "password": "",
23 | "port": 5432,
24 | "log_queries": False,
25 | "options": {
26 | #
27 | }
28 | },
29 | "sqlite": {
30 | "driver": "sqlite",
31 | "database": "masonite.sqlite3",
32 | }
33 | }
34 |
35 | DB = ConnectionResolver().set_connection_details(DATABASES)
36 |
--------------------------------------------------------------------------------
/databases/migrations/2018_01_09_043202_create_users_table.py:
--------------------------------------------------------------------------------
1 | from src.masoniteorm.migrations import Migration
2 | from tests.User import User
3 |
4 |
5 | class CreateUsersTable(Migration):
6 |
7 | def up(self):
8 | """Run the migrations."""
9 | with self.schema.create('users') as table:
10 | table.increments('id')
11 | table.string('name')
12 | table.string('email').unique()
13 | table.string('password')
14 | table.string('second_password').nullable()
15 | table.string('remember_token').nullable()
16 | table.timestamp('verified_at').nullable()
17 | table.timestamps()
18 |
19 | if not self.schema._dry:
20 | User.on(self.connection).set_schema(self.schema_name).create({
21 | 'name': 'Joe',
22 | 'email': 'joe@email.com',
23 | 'password': 'secret'
24 | })
25 |
26 | def down(self):
27 | """Revert the migrations."""
28 | self.schema.drop('users')
29 |
--------------------------------------------------------------------------------
/databases/migrations/2020_04_17_000000_create_friends_table.py:
--------------------------------------------------------------------------------
1 | from src.masoniteorm.migrations.Migration import Migration
2 |
3 | class CreateFriendsTable(Migration):
4 |
5 | def up(self):
6 | """
7 | Run the migrations.
8 | """
9 |
10 | with self.schema.create('friends') as table:
11 | table.increments('id')
12 | table.string('name')
13 | table.integer('age')
14 |
15 | def down(self):
16 | """
17 | Revert the migrations.
18 | """
19 | self.schema.drop('friends')
--------------------------------------------------------------------------------
/databases/migrations/2020_04_17_00000_create_articles_table.py:
--------------------------------------------------------------------------------
1 | from src.masoniteorm.migrations.Migration import Migration
2 |
3 | class CreateArticlesTable(Migration):
4 |
5 | def up(self):
6 | """
7 | Run the migrations.
8 | """
9 | with self.schema.create('fans') as table:
10 | table.increments('id')
11 | table.string('name')
12 | table.integer('age')
13 |
14 | def down(self):
15 | """
16 | Revert the migrations.
17 | """
18 | self.schema.drop('fans')
--------------------------------------------------------------------------------
/databases/migrations/2020_10_20_152904_create_table_schema_migration.py:
--------------------------------------------------------------------------------
1 | """CreateTableSchemaMigration Migration."""
2 |
3 | from src.masoniteorm.migrations import Migration
4 |
5 |
6 | class CreateTableSchemaMigration(Migration):
7 |
8 | def up(self):
9 | """
10 | Run the migrations.
11 | """
12 | with self.schema.create("table_schema") as table:
13 | table.increments('id')
14 | table.string('name')
15 | table.timestamps()
16 |
17 | def down(self):
18 | """
19 | Revert the migrations.
20 | """
21 | self.schema.drop("table_schema")
22 |
--------------------------------------------------------------------------------
/databases/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | sys.path.append(os.getcwd())
4 |
--------------------------------------------------------------------------------
/databases/seeds/database_seeder.py:
--------------------------------------------------------------------------------
1 | """Base Database Seeder Module."""
2 |
3 | from src.masoniteorm.seeds import Seeder
4 | from .user_table_seeder import UserTableSeeder
5 |
6 | class DatabaseSeeder(Seeder):
7 |
8 | def run(self):
9 | """Run the database seeds."""
10 | self.call(UserTableSeeder)
11 |
--------------------------------------------------------------------------------
/databases/seeds/user_table_seeder.py:
--------------------------------------------------------------------------------
1 | """UserTableSeeder Seeder."""
2 |
3 | from src.masoniteorm.seeds import Seeder
4 | from src.masoniteorm.factories import Factory as factory
5 | from tests.User import User
6 |
7 | factory.register(User, lambda faker: {'email': faker.email()})
8 |
9 | class UserTableSeeder(Seeder):
10 |
11 | def run(self):
12 | """Run the database seeds."""
13 | factory(User, 5).create({
14 | 'name': 'Joe',
15 | 'password': 'joe',
16 | })
17 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | init: .env .bootstrapped-pip .git/hooks/pre-commit
4 | init-ci:
5 | touch .ignore-pre-commit
6 | make init
7 |
8 | .bootstrapped-pip: requirements.txt requirements.dev
9 | pip install -r requirements.txt -r requirements.dev
10 | touch .bootstrapped-pip
11 |
12 | .git/hooks/pre-commit:
13 | @if ! test -e ".ignore-pre-commit"; then \
14 | pip install pre-commit; \
15 | pre-commit install --install-hooks; \
16 | fi
17 |
18 | .env:
19 | cp .env-example .env
20 |
21 | # Create MySQL Database
22 | # Create Postgres Database
23 | test: init
24 | python -m pytest tests
25 | ci:
26 | make test
27 | check: format sort lint
28 | lint:
29 | flake8 src/masoniteorm/
30 | format: init
31 | black src/masoniteorm tests/
32 | sort: init
33 | isort src/masoniteorm tests/
34 | coverage:
35 | python -m pytest --cov-report term --cov-report xml --cov=src/masoniteorm tests/
36 | python -m coveralls
37 | show:
38 | python -m pytest --cov-report term --cov-report html --cov=src/masoniteorm tests/
39 | cov:
40 | python -m pytest --cov-report term --cov-report xml --cov=src/masoniteorm tests/
41 | publish:
42 | pip install twine
43 | make test
44 | python setup.py sdist
45 | twine upload dist/*
46 | rm -fr build dist .egg masonite.egg-info
47 | rm -rf dist/*
48 | pub:
49 | python setup.py sdist
50 | twine upload dist/*
51 | rm -fr build dist .egg masonite.egg-info
52 | rm -rf dist/*
53 | pypirc:
54 | cp .pypirc ~/.pypirc
55 |
--------------------------------------------------------------------------------
/orm:
--------------------------------------------------------------------------------
1 | """Craft Command.
2 |
3 | This module is really used for backup only if the masonite CLI cannot import this for you.
4 | This can be used by running "python craft". This module is not ran when the CLI can
5 | successfully import commands for you.
6 | """
7 |
8 | from cleo import Application
9 | from src.masoniteorm.commands import (
10 | MigrateCommand,
11 | MigrateRollbackCommand,
12 | MigrateRefreshCommand,
13 | MigrateFreshCommand,
14 | MakeMigrationCommand,
15 | MakeObserverCommand,
16 | MakeModelCommand,
17 | MigrateStatusCommand,
18 | MigrateResetCommand,
19 | MakeSeedCommand,
20 | MakeModelDocstringCommand,
21 | SeedRunCommand,
22 | )
23 |
24 | application = Application("ORM Version:", 0.1)
25 |
26 | application.add(MigrateCommand())
27 | application.add(MigrateRollbackCommand())
28 | application.add(MigrateRefreshCommand())
29 | application.add(MigrateFreshCommand())
30 | application.add(MakeMigrationCommand())
31 | application.add(MakeModelCommand())
32 | application.add(MakeModelDocstringCommand())
33 | application.add(MakeObserverCommand())
34 | application.add(MigrateResetCommand())
35 | application.add(MigrateStatusCommand())
36 | application.add(MakeSeedCommand())
37 |
38 | application.add(SeedRunCommand())
39 |
40 | if __name__ == "__main__":
41 | application.run()
42 |
--------------------------------------------------------------------------------
/orm.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/orm.sqlite3
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | target-version = ['py38']
3 | include = '\.pyi?$'
4 | line-length = 79
5 |
6 | [tool.isort]
7 | profile = "black"
8 | multi_line_output = 3
9 | include_trailing_comma = true
10 | force_grid_wrap = 0
11 | use_parentheses = true
12 | ensure_newline_before_comments = true
13 |
14 | [tool.flake8]
15 | ignore = ['E501', 'E203', 'E128', 'E402', 'E731', 'F821', 'E712', 'W503', 'F811']
16 | #max-line-length = 79
17 | #max-complexity = 18
18 | per-file-ignores = [
19 | '__init__.py:F401',
20 | ]
21 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | env =
3 | D:DB_CONFIG_PATH=config/test-database
--------------------------------------------------------------------------------
/requirements.dev:
--------------------------------------------------------------------------------
1 | flake8-pyproject
2 | black
3 | faker
4 | pytest
5 | pytest-cov
6 | pytest-env
7 | pymysql
8 | isort
9 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | inflection==0.3.1
2 | psycopg2-binary
3 | pyodbc
4 | pendulum>=2.1,<3.1
5 | cleo>=0.8.0,<0.9
6 | python-dotenv==0.14.0
--------------------------------------------------------------------------------
/src/masoniteorm/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/src/masoniteorm/.gitignore
--------------------------------------------------------------------------------
/src/masoniteorm/__init__.py:
--------------------------------------------------------------------------------
1 | from .models import Model
2 | from .factories.Factory import Factory
3 |
--------------------------------------------------------------------------------
/src/masoniteorm/collection/__init__.py:
--------------------------------------------------------------------------------
1 | from .Collection import Collection
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/CanOverrideConfig.py:
--------------------------------------------------------------------------------
1 | from cleo import Command
2 |
3 |
4 | class CanOverrideConfig(Command):
5 | def __init__(self):
6 | super().__init__()
7 | self.add_option()
8 |
9 | def add_option(self):
10 | # 8 is the required flag constant in cleo
11 | self._config.add_option(
12 | "config",
13 | "C",
14 | 8,
15 | description="The path to the ORM configuration file. If not given DB_CONFIG_PATH env variable will be used and finally 'config.database'.",
16 | )
17 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/CanOverrideOptionsDefault.py:
--------------------------------------------------------------------------------
1 | from inflection import underscore
2 |
3 |
4 | class CanOverrideOptionsDefault:
5 | """Command mixin to allow to override optional argument default values when instantiating the
6 | command.
7 | Example: SomeCommand(app, option1="other/default").
8 |
9 | If an argument long name is using - then use _ in keyword argument:
10 | Example: SomeCommand(app, option_1="other/default") for an option named in option-1
11 | """
12 |
13 | def __init__(self, **kwargs):
14 | super().__init__()
15 | self.overriden_default = kwargs
16 | for option_name, option in self.config.options.items():
17 | # Cleo does not authorize _ in option name but - are authorized and unfortunately -
18 | # cannot be used in Python variables. So underscore() is called to make sure that
19 | # an option like 'option-a' will be accessible with 'option_a' in kwargs
20 | default = self.overriden_default.get(underscore(option_name))
21 | if default:
22 | option.set_default(default)
23 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/Command.py:
--------------------------------------------------------------------------------
1 | from .CanOverrideConfig import CanOverrideConfig
2 | from .CanOverrideOptionsDefault import CanOverrideOptionsDefault
3 |
4 |
5 | class Command(CanOverrideOptionsDefault, CanOverrideConfig):
6 | pass
7 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/Entry.py:
--------------------------------------------------------------------------------
1 | """Craft Command.
2 |
3 | This module is really used for backup only if the masonite CLI cannot import this for you.
4 | This can be used by running "python craft". This module is not ran when the CLI can
5 | successfully import commands for you.
6 | """
7 |
8 | from cleo import Application
9 | from . import (
10 | MigrateCommand,
11 | MigrateRollbackCommand,
12 | MigrateRefreshCommand,
13 | MigrateFreshCommand,
14 | MakeMigrationCommand,
15 | MakeModelCommand,
16 | MakeModelDocstringCommand,
17 | MakeObserverCommand,
18 | MigrateStatusCommand,
19 | MigrateResetCommand,
20 | MakeSeedCommand,
21 | SeedRunCommand,
22 | ShellCommand,
23 | )
24 |
25 | application = Application("ORM Version:", 0.1)
26 |
27 | application.add(MigrateCommand())
28 | application.add(MigrateRollbackCommand())
29 | application.add(MigrateRefreshCommand())
30 | application.add(MigrateFreshCommand())
31 | application.add(MakeMigrationCommand())
32 | application.add(MakeModelCommand())
33 | application.add(MakeModelDocstringCommand())
34 | application.add(MakeObserverCommand())
35 | application.add(MigrateResetCommand())
36 | application.add(MigrateStatusCommand())
37 | application.add(MakeSeedCommand())
38 | application.add(SeedRunCommand())
39 | application.add(ShellCommand())
40 |
41 | if __name__ == "__main__":
42 | application.run()
43 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MakeMigrationCommand.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 | import pathlib
4 |
5 | from inflection import camelize, tableize
6 |
7 | from .Command import Command
8 |
9 |
10 | class MakeMigrationCommand(Command):
11 | """
12 | Creates a new migration file.
13 |
14 | migration
15 | {name : The name of the migration}
16 | {--c|create=None : The table to create}
17 | {--t|table=None : The table to alter}
18 | {--d|directory=databases/migrations : The location of the migration directory}
19 | """
20 |
21 | def handle(self):
22 | name = self.argument("name")
23 | now = datetime.datetime.today()
24 |
25 | if self.option("create") != "None":
26 | table = self.option("create")
27 | stub_file = "create_migration"
28 | else:
29 | table = self.option("table")
30 | stub_file = "table_migration"
31 |
32 | if table == "None":
33 | table = tableize(name.replace("create_", "").replace("_table", ""))
34 | stub_file = "create_migration"
35 |
36 | migration_directory = self.option("directory")
37 |
38 | with open(
39 | os.path.join(
40 | pathlib.Path(__file__).parent.absolute(), f"stubs/{stub_file}.stub"
41 | )
42 | ) as fp:
43 | output = fp.read()
44 | output = output.replace("__MIGRATION_NAME__", camelize(name))
45 | output = output.replace("__TABLE_NAME__", table)
46 |
47 | file_name = f"{now.strftime('%Y_%m_%d_%H%M%S')}_{name}.py"
48 |
49 | with open(os.path.join(os.getcwd(), migration_directory, file_name), "w") as fp:
50 | fp.write(output)
51 |
52 | self.info(
53 | f"Migration file created: {os.path.join(migration_directory, file_name)}"
54 | )
55 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MakeModelCommand.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 |
4 | from inflection import camelize, tableize, underscore
5 |
6 | from .Command import Command
7 |
8 |
9 | class MakeModelCommand(Command):
10 | """
11 | Creates a new model file.
12 |
13 | model
14 | {name : The name of the model}
15 | {--m|migration : Optionally create a migration file}
16 | {--s|seeder : Optionally create a seeder file}
17 | {--c|create : If the migration file should create a table}
18 | {--t|table : If the migration file should modify an existing table}
19 | {--p|pep : Makes the file into pep 8 standards}
20 | {--d|directory=app : The location of the model directory}
21 | {--D|migrations-directory=databases/migrations : The location of the migration directory}
22 | {--S|seeders-directory=databases/seeds : The location of the seeders directory}
23 | """
24 |
25 | def handle(self):
26 | name = self.argument("name")
27 |
28 | model_directory = self.option("directory")
29 |
30 | with open(
31 | os.path.join(pathlib.Path(__file__).parent.absolute(), "stubs/model.stub")
32 | ) as fp:
33 | output = fp.read()
34 | output = output.replace("__CLASS__", camelize(name))
35 |
36 | if self.option("pep"):
37 | file_name = f"{underscore(name)}.py"
38 | else:
39 | file_name = f"{camelize(name)}.py"
40 |
41 | full_directory_path = os.path.join(os.getcwd(), model_directory)
42 |
43 | if os.path.exists(os.path.join(full_directory_path, file_name)):
44 | self.line(
45 | f'Model "{name}" Already Exists ({full_directory_path}/{file_name})'
46 | )
47 | return
48 |
49 | os.makedirs(os.path.dirname(os.path.join(full_directory_path)), exist_ok=True)
50 |
51 | with open(os.path.join(os.getcwd(), model_directory, file_name), "w+") as fp:
52 | fp.write(output)
53 |
54 | self.info(f"Model created: {os.path.join(model_directory, file_name)}")
55 | if self.option("migration"):
56 | migrations_directory = self.option("migrations-directory")
57 | if self.option("table"):
58 | self.call(
59 | "migration",
60 | f"update_{tableize(name)}_table --table {tableize(name)} --directory {migrations_directory}",
61 | )
62 | else:
63 | self.call(
64 | "migration",
65 | f"create_{tableize(name)}_table --create {tableize(name)} --directory {migrations_directory}",
66 | )
67 |
68 | if self.option("seeder"):
69 | directory = self.option("seeders-directory")
70 | self.call("seed", f"{self.argument('name')} --directory {directory}")
71 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MakeModelDocstringCommand.py:
--------------------------------------------------------------------------------
1 | from ..config import load_config
2 | from .Command import Command
3 |
4 |
5 | class MakeModelDocstringCommand(Command):
6 | """
7 | Generate model docstring and type hints (for auto-completion).
8 |
9 | model:docstring
10 | {table : The table you want to generate docstring and type hints}
11 | {--t|type-hints : The table you want to generate docstring and type hints}
12 | {--c|connection=default : The connection you want to use}
13 | """
14 |
15 | def handle(self):
16 | table = self.argument("table")
17 | DB = load_config(self.option("config")).DB
18 |
19 | schema = DB.get_schema_builder(self.option("connection"))
20 |
21 | if not schema.has_table(table):
22 | return self.line_error(
23 | f"There is no such table {table} for this connection."
24 | )
25 |
26 | self.info(f"Model Docstring for table: {table}")
27 | print('"""')
28 | for _, column in schema.get_columns(table).items():
29 | length = f"({column.length})" if column.length else ""
30 | default = f" default: {column.default}"
31 | print(f"{column.name}: {column.column_type}{length}{default}")
32 | print('"""')
33 |
34 | if self.option("type-hints"):
35 | self.info(f"Model Type Hints for table: {table}")
36 | for name, column in schema.get_columns(table).items():
37 | print(f" {name}:{column.column_python_type.__name__}")
38 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MakeObserverCommand.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 |
4 | from inflection import camelize, underscore
5 |
6 | from .Command import Command
7 |
8 |
9 | class MakeObserverCommand(Command):
10 | """
11 | Creates a new observer file.
12 |
13 | observer
14 | {name : The name of the observer}
15 | {--m|model=None : The name of the model}
16 | {--d|directory=app/observers : The location of the observers directory}
17 | """
18 |
19 | def handle(self):
20 | name = self.argument("name")
21 | model = self.option("model")
22 | if model == "None":
23 | model = name
24 |
25 | observer_directory = self.option("directory")
26 |
27 | with open(
28 | os.path.join(
29 | pathlib.Path(__file__).parent.absolute(), "stubs/observer.stub"
30 | )
31 | ) as fp:
32 | output = fp.read()
33 | output = output.replace("__CLASS__", camelize(name))
34 | output = output.replace("__MODEL_VARIABLE__", underscore(model))
35 | output = output.replace("__MODEL__", camelize(model))
36 |
37 | file_name = f"{camelize(name)}Observer.py"
38 |
39 | full_directory_path = os.path.join(os.getcwd(), observer_directory)
40 |
41 | if os.path.exists(os.path.join(full_directory_path, file_name)):
42 | self.line(
43 | f'Observer "{name}" Already Exists ({full_directory_path}/{file_name})'
44 | )
45 | return
46 |
47 | os.makedirs(os.path.join(full_directory_path), exist_ok=True)
48 |
49 | with open(os.path.join(os.getcwd(), observer_directory, file_name), "w+") as fp:
50 | fp.write(output)
51 |
52 | self.info(f"Observer created: {file_name}")
53 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MakeSeedCommand.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 |
4 | from inflection import camelize, underscore
5 |
6 | from .Command import Command
7 |
8 |
9 | class MakeSeedCommand(Command):
10 | """
11 | Creates a new seed file.
12 |
13 | seed
14 | {name : The name of the seed}
15 | {--d|directory=databases/seeds : The location of the seed directory}
16 | """
17 |
18 | def handle(self):
19 | # get the contents of a stub file
20 | # replace the placeholders of a stub file
21 | # output the content to a file location
22 | name = self.argument("name") + "TableSeeder"
23 | seed_directory = self.option("directory")
24 |
25 | file_name = underscore(name)
26 | stub_file = "create_seed"
27 |
28 | with open(
29 | os.path.join(
30 | pathlib.Path(__file__).parent.absolute(), f"stubs/{stub_file}.stub"
31 | )
32 | ) as fp:
33 | output = fp.read()
34 | output = output.replace("__SEEDER_NAME__", camelize(name))
35 |
36 | file_name = f"{underscore(name)}.py"
37 | full_path = pathlib.Path(os.path.join(os.getcwd(), seed_directory, file_name))
38 |
39 | path_normalized = pathlib.Path(seed_directory) / pathlib.Path(file_name)
40 |
41 | if os.path.exists(full_path):
42 | return self.line(f"{path_normalized} already exists.")
43 |
44 | with open(full_path, "w") as fp:
45 | fp.write(output)
46 |
47 | self.info(f"Seed file created: {path_normalized}")
48 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateCommand.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from ..migrations import Migration
4 | from .Command import Command
5 |
6 |
7 | class MigrateCommand(Command):
8 | """
9 | Run migrations.
10 |
11 | migrate
12 | {--m|migration=all : Migration's name to be migrated}
13 | {--c|connection=default : The connection you want to run migrations on}
14 | {--f|force : Force migrations without prompt in production}
15 | {--s|show : Shows the output of SQL for migrations that would be running}
16 | {--schema=? : Sets the schema to be migrated}
17 | {--d|directory=databases/migrations : The location of the migration directory}
18 | """
19 |
20 | def handle(self):
21 | # prompt user for confirmation in production
22 | if os.getenv("APP_ENV") == "production" and not self.option("force"):
23 | answer = ""
24 | while answer not in ["y", "n"]:
25 | answer = input(
26 | "Do you want to run migrations in PRODUCTION ? (y/n)\n"
27 | ).lower()
28 | if answer != "y":
29 | self.info("Migrations cancelled")
30 | exit(0)
31 | migration = Migration(
32 | command_class=self,
33 | connection=self.option("connection"),
34 | migration_directory=self.option("directory"),
35 | config_path=self.option("config"),
36 | schema=self.option("schema"),
37 | )
38 | migration.create_table_if_not_exists()
39 | if not migration.get_unran_migrations():
40 | self.info("Nothing To Migrate!")
41 | return
42 |
43 | migration_name = self.option("migration")
44 | show_output = self.option("show")
45 |
46 | migration.migrate(migration=migration_name, output=show_output)
47 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateFreshCommand.py:
--------------------------------------------------------------------------------
1 | from ..migrations import Migration
2 |
3 | from .Command import Command
4 |
5 |
6 | class MigrateFreshCommand(Command):
7 | """
8 | Drops all tables and migrates them again.
9 |
10 | migrate:fresh
11 | {--c|connection=default : The connection you want to run migrations on}
12 | {--d|directory=databases/migrations : The location of the migration directory}
13 | {--f|ignore-fk=? : The connection you want to run migrations on}
14 | {--s|seed=? : Seed database after fresh. The seeder to be ran can be provided in argument}
15 | {--schema=? : Sets the schema to be migrated}
16 | {--D|seed-directory=databases/seeds : The location of the seed directory if seed option is used.}
17 | """
18 |
19 | def handle(self):
20 | migration = Migration(
21 | command_class=self,
22 | connection=self.option("connection"),
23 | migration_directory=self.option("directory"),
24 | config_path=self.option("config"),
25 | schema=self.option("schema"),
26 | )
27 |
28 | migration.fresh(ignore_fk=self.option("ignore-fk"))
29 |
30 | self.line("")
31 |
32 | if self.option("seed") == "null":
33 | self.call(
34 | "seed:run",
35 | f"None --directory {self.option('seed-directory')} --connection {self.option('connection')}",
36 | )
37 |
38 | elif self.option("seed"):
39 | self.call(
40 | "seed:run",
41 | f"{self.option('seed')} --directory {self.option('seed-directory')} --connection {self.option('connection')}",
42 | )
43 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateRefreshCommand.py:
--------------------------------------------------------------------------------
1 | from ..migrations import Migration
2 |
3 | from .Command import Command
4 |
5 |
6 | class MigrateRefreshCommand(Command):
7 | """
8 | Rolls back migrations and migrates them again.
9 |
10 | migrate:refresh
11 | {--m|migration=all : Migration's name to be refreshed}
12 | {--c|connection=default : The connection you want to run migrations on}
13 | {--d|directory=databases/migrations : The location of the migration directory}
14 | {--s|seed=? : Seed database after refresh. The seeder to be ran can be provided in argument}
15 | {--schema=? : Sets the schema to be migrated}
16 | {--D|seed-directory=databases/seeds : The location of the seed directory if seed option is used.}
17 | """
18 |
19 | def handle(self):
20 | migration = Migration(
21 | command_class=self,
22 | connection=self.option("connection"),
23 | migration_directory=self.option("directory"),
24 | config_path=self.option("config"),
25 | schema=self.option("schema"),
26 | )
27 |
28 | migration.refresh(self.option("migration"))
29 |
30 | self.line("")
31 |
32 | if self.option("seed") == "null":
33 | self.call(
34 | "seed:run",
35 | f"None --directory {self.option('seed-directory')} --connection {self.option('connection')}",
36 | )
37 |
38 | elif self.option("seed"):
39 | self.call(
40 | "seed:run",
41 | f"{self.option('seed')} --directory {self.option('seed-directory')} --connection {self.option('connection')}",
42 | )
43 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateResetCommand.py:
--------------------------------------------------------------------------------
1 | from ..migrations import Migration
2 | from .Command import Command
3 |
4 |
5 | class MigrateResetCommand(Command):
6 | """
7 | Reset migrations.
8 |
9 | migrate:reset
10 | {--m|migration=all : Migration's name to be rollback}
11 | {--c|connection=default : The connection you want to run migrations on}
12 | {--schema=? : Sets the schema to be migrated}
13 | {--d|directory=databases/migrations : The location of the migration directory}
14 | """
15 |
16 | def handle(self):
17 | migration = Migration(
18 | command_class=self,
19 | connection=self.option("connection"),
20 | migration_directory=self.option("directory"),
21 | config_path=self.option("config"),
22 | schema=self.option("schema"),
23 | )
24 |
25 | migration.reset(self.option("migration"))
26 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateRollbackCommand.py:
--------------------------------------------------------------------------------
1 | from ..migrations import Migration
2 | from .Command import Command
3 |
4 |
5 | class MigrateRollbackCommand(Command):
6 | """
7 | Rolls back the last batch of migrations.
8 |
9 | migrate:rollback
10 | {--m|migration=all : Migration's name to be rollback}
11 | {--c|connection=default : The connection you want to run migrations on}
12 | {--s|show : Shows the output of SQL for migrations that would be running}
13 | {--schema=? : Sets the schema to be migrated}
14 | {--d|directory=databases/migrations : The location of the migration directory}
15 | """
16 |
17 | def handle(self):
18 | Migration(
19 | command_class=self,
20 | connection=self.option("connection"),
21 | migration_directory=self.option("directory"),
22 | config_path=self.option("config"),
23 | schema=self.option("schema"),
24 | ).rollback(migration=self.option("migration"), output=self.option("show"))
25 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/MigrateStatusCommand.py:
--------------------------------------------------------------------------------
1 | from ..migrations import Migration
2 | from .Command import Command
3 |
4 |
5 | class MigrateStatusCommand(Command):
6 | """
7 | Display migrations status.
8 |
9 | migrate:status
10 | {--c|connection=default : The connection you want to run migrations on}
11 | {--schema=? : Sets the schema to be migrated}
12 | {--d|directory=databases/migrations : The location of the migration directory}
13 | """
14 |
15 | def handle(self):
16 | migration = Migration(
17 | command_class=self,
18 | connection=self.option("connection"),
19 | migration_directory=self.option("directory"),
20 | config_path=self.option("config"),
21 | schema=self.option("schema"),
22 | )
23 | migration.create_table_if_not_exists()
24 | table = self.table()
25 | table.set_header_row(["Ran?", "Migration", "Batch"])
26 | migrations = []
27 |
28 | for migration_data in migration.get_ran_migrations():
29 | migration_file = migration_data["migration_file"]
30 | batch = migration_data["batch"]
31 |
32 | migrations.append(
33 | [
34 | "Y",
35 | f"{migration_file}",
36 | f"{batch}",
37 | ]
38 | )
39 |
40 | for migration_file in migration.get_unran_migrations():
41 | migrations.append(
42 | [
43 | "N",
44 | f"{migration_file}",
45 | "-",
46 | ]
47 | )
48 |
49 | table.set_rows(migrations)
50 |
51 | table.render(self.io)
52 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/SeedRunCommand.py:
--------------------------------------------------------------------------------
1 | from inflection import camelize, underscore
2 |
3 | from ..seeds import Seeder
4 | from .Command import Command
5 |
6 |
7 | class SeedRunCommand(Command):
8 | """
9 | Run seeds.
10 |
11 | seed:run
12 | {--c|connection=default : The connection you want to run migrations on}
13 | {--dry : If the seed should run in dry mode}
14 | {table=None : Name of the table to seed}
15 | {--d|directory=databases/seeds : The location of the seed directory}
16 | """
17 |
18 | def handle(self):
19 | seeder = Seeder(
20 | dry=self.option("dry"),
21 | seed_path=self.option("directory"),
22 | connection=self.option("connection"),
23 | )
24 |
25 | if self.argument("table") == "None":
26 | seeder.run_database_seed()
27 | seeder_seeded = "Database Seeder"
28 |
29 | else:
30 | table = self.argument("table")
31 | seeder_file = (
32 | f"{underscore(table)}_table_seeder.{camelize(table)}TableSeeder"
33 | )
34 | seeder.run_specific_seed(seeder_file)
35 | seeder_seeded = f"{camelize(table)}TableSeeder"
36 |
37 | self.line(f"{seeder_seeded} seeded!")
38 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | sys.path.append(os.getcwd())
5 |
6 | from .MigrateCommand import MigrateCommand
7 | from .MigrateRollbackCommand import MigrateRollbackCommand
8 | from .MigrateRefreshCommand import MigrateRefreshCommand
9 | from .MigrateFreshCommand import MigrateFreshCommand
10 | from .MigrateResetCommand import MigrateResetCommand
11 | from .MakeModelCommand import MakeModelCommand
12 | from .MakeModelDocstringCommand import MakeModelDocstringCommand
13 | from .MakeObserverCommand import MakeObserverCommand
14 | from .MigrateStatusCommand import MigrateStatusCommand
15 | from .MakeMigrationCommand import MakeMigrationCommand
16 | from .MakeSeedCommand import MakeSeedCommand
17 | from .SeedRunCommand import SeedRunCommand
18 | from .ShellCommand import ShellCommand
19 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/stubs/create_migration.stub:
--------------------------------------------------------------------------------
1 | """__MIGRATION_NAME__ Migration."""
2 |
3 | from masoniteorm.migrations import Migration
4 |
5 |
6 | class __MIGRATION_NAME__(Migration):
7 | def up(self):
8 | """
9 | Run the migrations.
10 | """
11 | with self.schema.create("__TABLE_NAME__") as table:
12 | table.increments("id")
13 |
14 | table.timestamps()
15 |
16 | def down(self):
17 | """
18 | Revert the migrations.
19 | """
20 | self.schema.drop("__TABLE_NAME__")
21 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/stubs/create_seed.stub:
--------------------------------------------------------------------------------
1 | """__SEEDER_NAME__ Seeder."""
2 |
3 | from masoniteorm.seeds import Seeder
4 |
5 |
6 | class __SEEDER_NAME__(Seeder):
7 | def run(self):
8 | """Run the database seeds."""
9 | pass
10 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/stubs/model.stub:
--------------------------------------------------------------------------------
1 | """ __CLASS__ Model """
2 |
3 | from masoniteorm.models import Model
4 |
5 |
6 | class __CLASS__(Model):
7 | """__CLASS__ Model"""
8 |
9 | pass
10 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/stubs/observer.stub:
--------------------------------------------------------------------------------
1 | """__CLASS__ Observer"""
2 |
3 | from masoniteorm.models import Model
4 |
5 |
6 | class __CLASS__Observer:
7 | def created(self, __MODEL_VARIABLE__):
8 | """Handle the __MODEL__ "created" event.
9 |
10 | Args:
11 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
12 | """
13 | pass
14 |
15 | def creating(self, __MODEL_VARIABLE__):
16 | """Handle the __MODEL__ "creating" event.
17 |
18 | Args:
19 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
20 | """
21 | pass
22 |
23 | def saving(self, __MODEL_VARIABLE__):
24 | """Handle the __MODEL__ "saving" event.
25 |
26 | Args:
27 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
28 | """
29 | pass
30 |
31 | def saved(self, __MODEL_VARIABLE__):
32 | """Handle the __MODEL__ "saved" event.
33 |
34 | Args:
35 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
36 | """
37 | pass
38 |
39 | def updating(self, __MODEL_VARIABLE__):
40 | """Handle the __MODEL__ "updating" event.
41 |
42 | Args:
43 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
44 | """
45 | pass
46 |
47 | def updated(self, __MODEL_VARIABLE__):
48 | """Handle the __MODEL__ "updated" event.
49 |
50 | Args:
51 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
52 | """
53 | pass
54 |
55 | def booted(self, __MODEL_VARIABLE__):
56 | """Handle the __MODEL__ "booted" event.
57 |
58 | Args:
59 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
60 | """
61 | pass
62 |
63 | def booting(self, __MODEL_VARIABLE__):
64 | """Handle the __MODEL__ "booting" event.
65 |
66 | Args:
67 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
68 | """
69 | pass
70 |
71 | def hydrating(self, __MODEL_VARIABLE__):
72 | """Handle the __MODEL__ "hydrating" event.
73 |
74 | Args:
75 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
76 | """
77 | pass
78 |
79 | def hydrated(self, __MODEL_VARIABLE__):
80 | """Handle the __MODEL__ "hydrated" event.
81 |
82 | Args:
83 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
84 | """
85 | pass
86 |
87 | def deleting(self, __MODEL_VARIABLE__):
88 | """Handle the __MODEL__ "deleting" event.
89 |
90 | Args:
91 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
92 | """
93 | pass
94 |
95 | def deleted(self, __MODEL_VARIABLE__):
96 | """Handle the __MODEL__ "deleted" event.
97 |
98 | Args:
99 | __MODEL_VARIABLE__ (masoniteorm.models.Model): __MODEL__ model.
100 | """
101 | pass
102 |
--------------------------------------------------------------------------------
/src/masoniteorm/commands/stubs/table_migration.stub:
--------------------------------------------------------------------------------
1 | """__MIGRATION_NAME__ Migration."""
2 |
3 | from masoniteorm.migrations import Migration
4 |
5 |
6 | class __MIGRATION_NAME__(Migration):
7 | def up(self):
8 | """
9 | Run the migrations.
10 | """
11 | with self.schema.table("__TABLE_NAME__") as table:
12 | pass
13 |
14 | def down(self):
15 | """
16 | Revert the migrations.
17 | """
18 | with self.schema.table("__TABLE_NAME__") as table:
19 | pass
20 |
--------------------------------------------------------------------------------
/src/masoniteorm/connections/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/src/masoniteorm/connections/.gitignore
--------------------------------------------------------------------------------
/src/masoniteorm/connections/BaseConnection.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from timeit import default_timer as timer
3 | from .ConnectionResolver import ConnectionResolver
4 |
5 |
6 | class BaseConnection:
7 | _connection = None
8 | _cursor = None
9 | _dry = False
10 |
11 | def dry(self):
12 | self._dry = True
13 | return self
14 |
15 | def set_schema(self, schema):
16 | self.schema = schema
17 | return self
18 |
19 | def log(
20 | self, query, bindings, query_time=0, logger="masoniteorm.connections.queries"
21 | ):
22 | logger = logging.getLogger("masoniteorm.connection.queries")
23 | logger.propagate = self.full_details.get("propagate", True)
24 |
25 | logger.debug(
26 | f"Running query {query}, {bindings}. Executed in {query_time}ms",
27 | extra={"query": query, "bindings": bindings, "query_time": query_time},
28 | )
29 |
30 | def statement(self, query, bindings=()):
31 | """Wrapper around calling the cursor query. Helpful for logging output.
32 |
33 | Args:
34 | query (string): The query to execute on the cursor
35 | bindings (tuple, optional): Tuple of query bindings. Defaults to ().
36 | """
37 | start = timer()
38 | if not self._cursor:
39 | raise AttributeError(
40 | f"Must set the _cursor attribute on the {self.__class__.__name__} class before calling the 'statement' method."
41 | )
42 |
43 | self._cursor.execute(query, bindings)
44 | end = "{:.2f}".format(timer() - start)
45 |
46 | if self.full_details and self.full_details.get("log_queries", False):
47 | self.log(query, bindings, query_time=end)
48 |
49 | def has_global_connection(self):
50 | return self.name in ConnectionResolver().get_global_connections()
51 |
52 | def get_global_connection(self):
53 | return ConnectionResolver().get_global_connections()[self.name]
54 |
55 | def enable_query_log(self):
56 | self.full_details["log_queries"] = True
57 |
58 | def disable_query_log(self):
59 | self.full_details["log_queries"] = False
60 |
61 | def format_cursor_results(self, cursor_result):
62 | return cursor_result
63 |
64 | def set_cursor(self):
65 | self._cursor = self._connection.cursor()
66 | return self
67 |
68 | def select_many(self, query, bindings, amount):
69 | self.set_cursor()
70 | self.statement(query)
71 | if not self.open:
72 | self.make_connection()
73 |
74 | result = self.format_cursor_results(self._cursor.fetchmany(amount))
75 | while result:
76 | yield result
77 |
78 | result = self.format_cursor_results(self._cursor.fetchmany(amount))
79 |
80 | def enable_disable_foreign_keys(self):
81 | foreign_keys = self.full_details.get("foreign_keys")
82 | platform = self.get_default_platform()()
83 |
84 | if foreign_keys:
85 | self._connection.execute(platform.enable_foreign_key_constraints())
86 | elif foreign_keys is not None:
87 | self._connection.execute(platform.disable_foreign_key_constraints())
88 |
--------------------------------------------------------------------------------
/src/masoniteorm/connections/ConnectionFactory.py:
--------------------------------------------------------------------------------
1 | from ..config import load_config
2 |
3 |
4 | class ConnectionFactory:
5 | """Class for controlling the registration and creation of connection types."""
6 |
7 | _connections = {}
8 |
9 | def __init__(self, config_path=None):
10 | self.config_path = config_path
11 |
12 | @classmethod
13 | def register(cls, key, connection):
14 | """Registers new connections
15 |
16 | Arguments:
17 | key {key} -- The key or driver name you want assigned to this connection
18 | connection {masoniteorm.connections.BaseConnection} -- An instance of a BaseConnection class.
19 |
20 | Returns:
21 | cls
22 | """
23 | cls._connections.update({key: connection})
24 | return cls
25 |
26 | def make(self, key):
27 | """Makes already registered connections
28 |
29 | Arguments:
30 | key {string} -- The name of the connection you want to make
31 |
32 | Raises:
33 | Exception: Raises exception if there are no driver keys that match
34 |
35 | Returns:
36 | masoniteorm.connection.BaseConnection -- Returns an instance of a BaseConnection class.
37 | """
38 |
39 | DB = load_config(config_path=self.config_path).DB
40 |
41 | connections = DB.get_connection_details()
42 |
43 | if key == "default":
44 | connection_details = connections.get(connections.get("default"))
45 | connection = self._connections.get(connection_details.get("driver"))
46 | else:
47 | connection_details = connections.get(key)
48 | connection = self._connections.get(key)
49 |
50 | if connection:
51 | return connection
52 |
53 | raise Exception(
54 | "The '{connection}' connection does not exist".format(connection=key)
55 | )
56 |
--------------------------------------------------------------------------------
/src/masoniteorm/connections/__init__.py:
--------------------------------------------------------------------------------
1 | from .ConnectionResolver import ConnectionResolver
2 | from .ConnectionFactory import ConnectionFactory
3 | from .MySQLConnection import MySQLConnection
4 | from .PostgresConnection import PostgresConnection
5 | from .SQLiteConnection import SQLiteConnection
6 | from .MSSQLConnection import MSSQLConnection
7 |
--------------------------------------------------------------------------------
/src/masoniteorm/exceptions.py:
--------------------------------------------------------------------------------
1 | class DriverNotFound(Exception):
2 | pass
3 |
4 |
5 | class ModelNotFound(Exception):
6 | pass
7 |
8 |
9 | class HTTP404(Exception):
10 | pass
11 |
12 |
13 | class ConnectionNotRegistered(Exception):
14 | pass
15 |
16 |
17 | class QueryException(Exception):
18 | pass
19 |
20 |
21 | class MigrationNotFound(Exception):
22 | pass
23 |
24 |
25 | class ConfigurationNotFound(Exception):
26 | pass
27 |
28 |
29 | class InvalidUrlConfiguration(Exception):
30 | pass
31 |
32 |
33 | class MultipleRecordsFound(Exception):
34 | pass
35 |
36 |
37 | class InvalidArgument(Exception):
38 | pass
39 |
--------------------------------------------------------------------------------
/src/masoniteorm/expressions/__init__.py:
--------------------------------------------------------------------------------
1 | from .expressions import Raw
2 | from .expressions import JoinClause
3 |
--------------------------------------------------------------------------------
/src/masoniteorm/factories/Factory.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 | import random
3 |
4 |
5 | class Factory:
6 | _factories = {}
7 | _after_creates = {}
8 | _faker = None
9 |
10 | @property
11 | def faker(self):
12 | if not Factory._faker:
13 | Factory._faker = Faker()
14 | random.seed()
15 | Factory._faker.seed_instance(random.randint(1, 10000))
16 |
17 | return Factory._faker
18 |
19 | def __init__(self, model, number=1):
20 | self.model = model
21 | self.number = number
22 |
23 | def make(self, dictionary=None, name="default"):
24 | if dictionary is None:
25 | dictionary = {}
26 |
27 | if self.number == 1 and not isinstance(dictionary, list):
28 | called = self._factories[self.model][name](self.faker)
29 | called.update(dictionary)
30 | model = self.model.hydrate(called)
31 | self.run_after_creates(model)
32 | return model
33 | elif isinstance(dictionary, list):
34 | results = []
35 | for index in range(0, len(dictionary)):
36 | called = self._factories[self.model][name](self.faker)
37 | called.update(dictionary)
38 | results.append(called)
39 | models = self.model.hydrate(results)
40 | for model in models:
41 | self.run_after_creates(model)
42 | return models
43 |
44 | else:
45 | results = []
46 | for index in range(0, self.number):
47 | called = self._factories[self.model][name](self.faker)
48 | called.update(dictionary)
49 | results.append(called)
50 | models = self.model.hydrate(results)
51 | for model in models:
52 | self.run_after_creates(model)
53 | return models
54 |
55 | def create(self, dictionary=None, name="default"):
56 | if dictionary is None:
57 | dictionary = {}
58 |
59 | if self.number == 1 and not isinstance(dictionary, list):
60 | called = self._factories[self.model][name](self.faker)
61 | called.update(dictionary)
62 | model = self.model.create(called)
63 | self.run_after_creates(model)
64 | return model
65 | elif isinstance(dictionary, list):
66 | results = []
67 | for index in range(0, len(dictionary)):
68 | called = self._factories[self.model][name](self.faker)
69 | called.update(dictionary)
70 | results.append(called)
71 |
72 | models = self.model.create(results)
73 | for model in models:
74 | self.run_after_creates(model)
75 | return models
76 | else:
77 | full_collection = []
78 | for index in range(0, self.number):
79 | called = self._factories[self.model][name](self.faker)
80 | called.update(dictionary)
81 | full_collection.append(called)
82 | model = self.model.create(called)
83 | self.run_after_creates(model)
84 |
85 | return self.model.hydrate(full_collection)
86 |
87 | @classmethod
88 | def register(cls, model, call, name="default"):
89 | if model not in cls._factories:
90 | cls._factories[model] = {name: call}
91 | else:
92 | cls._factories[model][name] = call
93 |
94 | @classmethod
95 | def after_creating(cls, model, call, name="default"):
96 | if model not in cls._after_creates:
97 | cls._after_creates[model] = {name: call}
98 | else:
99 | cls._after_creates[model][name] = call
100 |
101 | def run_after_creates(self, model):
102 | if self.model not in self._after_creates:
103 | return model
104 |
105 | for name, callback in self._after_creates[self.model].items():
106 | callback(model, self.faker)
107 |
--------------------------------------------------------------------------------
/src/masoniteorm/factories/__init__.py:
--------------------------------------------------------------------------------
1 | from .Factory import Factory
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/helpers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/src/masoniteorm/helpers/__init__.py
--------------------------------------------------------------------------------
/src/masoniteorm/helpers/misc.py:
--------------------------------------------------------------------------------
1 | """Module for miscellaneous helper methods."""
2 |
3 | import warnings
4 |
5 |
6 | def deprecated(message):
7 | warnings.simplefilter("default", DeprecationWarning)
8 |
9 | def deprecated_decorator(func):
10 | def deprecated_func(*args, **kwargs):
11 | warnings.warn(
12 | "{} is a deprecated function. {}".format(func.__name__, message),
13 | category=DeprecationWarning,
14 | stacklevel=2,
15 | )
16 | return func(*args, **kwargs)
17 |
18 | return deprecated_func
19 |
20 | return deprecated_decorator
21 |
--------------------------------------------------------------------------------
/src/masoniteorm/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | from .Migration import Migration
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/models/MigrationModel.py:
--------------------------------------------------------------------------------
1 | from .Model import Model
2 |
3 |
4 | class MigrationModel(Model):
5 | __table__ = "migrations"
6 | __fillable__ = ["migration", "batch"]
7 | __timestamps__ = None
8 |
9 | __primary_key__ = "migration_id"
10 |
--------------------------------------------------------------------------------
/src/masoniteorm/models/Pivot.py:
--------------------------------------------------------------------------------
1 | from .Model import Model
2 |
3 |
4 | class Pivot(Model):
5 | __primary_key__ = "id"
6 |
--------------------------------------------------------------------------------
/src/masoniteorm/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .Model import Model
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/observers/ObservesEvents.py:
--------------------------------------------------------------------------------
1 | class ObservesEvents:
2 | def observe_events(self, model, event):
3 | if model.__has_events__ == True:
4 | for observer in model.__observers__.get(model.__class__, []):
5 | try:
6 | getattr(observer, event)(model)
7 | except AttributeError:
8 | pass
9 |
10 | @classmethod
11 | def observe(cls, observer):
12 | if cls in cls.__observers__:
13 | cls.__observers__[cls].append(observer)
14 | else:
15 | cls.__observers__.update({cls: [observer]})
16 |
17 | @classmethod
18 | def without_events(cls):
19 | """Sets __has_events__ attribute on model to false."""
20 | cls.__has_events__ = False
21 | return cls
22 |
23 | @classmethod
24 | def with_events(cls):
25 | """Sets __has_events__ attribute on model to True."""
26 | cls.__has_events__ = True
27 | return cls
28 |
--------------------------------------------------------------------------------
/src/masoniteorm/observers/__init__.py:
--------------------------------------------------------------------------------
1 | from .ObservesEvents import ObservesEvents
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/pagination/BasePaginator.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | class BasePaginator:
5 | def __iter__(self):
6 | for result in self.result:
7 | yield result
8 |
9 | def to_json(self):
10 | return json.dumps(self.serialize())
11 |
--------------------------------------------------------------------------------
/src/masoniteorm/pagination/LengthAwarePaginator.py:
--------------------------------------------------------------------------------
1 | import math
2 | from .BasePaginator import BasePaginator
3 |
4 |
5 | class LengthAwarePaginator(BasePaginator):
6 | def __init__(self, result, per_page, current_page, total, url=None):
7 | self.result = result
8 | self.current_page = current_page
9 | self.per_page = per_page
10 | self.count = len(self.result)
11 | self.last_page = int(math.ceil(total / per_page))
12 | self.next_page = (int(self.current_page) + 1) if self.has_more_pages() else None
13 | self.previous_page = (int(self.current_page) - 1) or None
14 | self.total = total
15 | self.url = url
16 |
17 | def serialize(self, *args, **kwargs):
18 | return {
19 | "data": self.result.serialize(*args, **kwargs),
20 | "meta": {
21 | "total": self.total,
22 | "next_page": self.next_page,
23 | "count": self.count,
24 | "previous_page": self.previous_page,
25 | "last_page": self.last_page,
26 | "current_page": self.current_page,
27 | },
28 | }
29 |
30 | def has_more_pages(self):
31 | return self.current_page < self.last_page
32 |
--------------------------------------------------------------------------------
/src/masoniteorm/pagination/SimplePaginator.py:
--------------------------------------------------------------------------------
1 | from .BasePaginator import BasePaginator
2 |
3 |
4 | class SimplePaginator(BasePaginator):
5 | def __init__(self, result, per_page, current_page, url=None):
6 | self.result = result
7 | self.current_page = current_page
8 | self.per_page = per_page
9 | self.count = len(self.result)
10 | self.next_page = (int(self.current_page) + 1) if self.has_more_pages() else None
11 | self.previous_page = (int(self.current_page) - 1) or None
12 | self.url = url
13 |
14 | def serialize(self, *args, **kwargs):
15 | return {
16 | "data": self.result.serialize(*args, **kwargs),
17 | "meta": {
18 | "next_page": self.next_page,
19 | "count": self.count,
20 | "previous_page": self.previous_page,
21 | "current_page": self.current_page,
22 | },
23 | }
24 |
25 | def has_more_pages(self):
26 | return len(self.result) > self.per_page
27 |
--------------------------------------------------------------------------------
/src/masoniteorm/pagination/__init__.py:
--------------------------------------------------------------------------------
1 | from .LengthAwarePaginator import LengthAwarePaginator
2 | from .SimplePaginator import SimplePaginator
3 |
--------------------------------------------------------------------------------
/src/masoniteorm/providers/ORMProvider.py:
--------------------------------------------------------------------------------
1 | from masonite.providers import Provider
2 |
3 | from masoniteorm.commands import (
4 | MigrateCommand,
5 | MigrateRollbackCommand,
6 | MigrateRefreshCommand,
7 | MigrateResetCommand,
8 | MakeModelCommand,
9 | MakeObserverCommand,
10 | MigrateStatusCommand,
11 | MakeMigrationCommand,
12 | MakeSeedCommand,
13 | SeedRunCommand,
14 | )
15 |
16 |
17 | class ORMProvider(Provider):
18 | """Masonite ORM database provider to be used inside
19 | Masonite based projects."""
20 |
21 | def __init__(self, application):
22 | self.application = application
23 |
24 | def register(self):
25 | self.application.make("commands").add(
26 | MakeMigrationCommand(),
27 | MakeSeedCommand(),
28 | MakeObserverCommand(),
29 | MigrateCommand(),
30 | MigrateResetCommand(),
31 | MakeModelCommand(),
32 | MigrateStatusCommand(),
33 | MigrateRefreshCommand(),
34 | MigrateRollbackCommand(),
35 | SeedRunCommand(),
36 | ),
37 |
38 | def boot(self):
39 | pass
40 |
--------------------------------------------------------------------------------
/src/masoniteorm/providers/__init__.py:
--------------------------------------------------------------------------------
1 | from .ORMProvider import ORMProvider
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/EagerRelation.py:
--------------------------------------------------------------------------------
1 | class EagerRelations:
2 | def __init__(self, relation=None):
3 | self.eagers = []
4 | self.nested_eagers = {}
5 | self.callback_eagers = {}
6 | self.is_nested = False
7 | self.relation = relation
8 |
9 | def register(self, *relations, callback=None):
10 | for relation in relations:
11 | if isinstance(relation, str) and "." not in relation:
12 | self.eagers += [relation]
13 | elif isinstance(relation, str) and "." in relation:
14 | self.is_nested = True
15 | relation_key = relation.split(".")[0]
16 | if relation_key not in self.nested_eagers:
17 | self.nested_eagers = {relation_key: relation.split(".")[1:]}
18 | else:
19 | self.nested_eagers[relation_key] += relation.split(".")[1:]
20 | elif isinstance(relation, (tuple, list)):
21 | for eagers in relations:
22 | for eager in eagers:
23 | self.register(eager)
24 | elif isinstance(relation, dict):
25 | self.callback_eagers.update(relation)
26 |
27 | return self
28 |
29 | def get_eagers(self):
30 | eagers = []
31 | if self.eagers:
32 | eagers.append(self.eagers)
33 |
34 | if self.nested_eagers:
35 | eagers.append(self.nested_eagers)
36 |
37 | if self.callback_eagers:
38 | eagers.append(self.callback_eagers)
39 |
40 | return eagers
41 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/__init__.py:
--------------------------------------------------------------------------------
1 | from .QueryBuilder import QueryBuilder
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/grammars/__init__.py:
--------------------------------------------------------------------------------
1 | from .MSSQLGrammar import MSSQLGrammar
2 | from .MySQLGrammar import MySQLGrammar
3 | from .PostgresGrammar import PostgresGrammar
4 | from .SQLiteGrammar import SQLiteGrammar
5 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/processors/MSSQLPostProcessor.py:
--------------------------------------------------------------------------------
1 | class MSSQLPostProcessor:
2 | """Post processor classes are responsable for modifying the result after a query.
3 |
4 | Post Processors are called after the connection calls the database in the
5 | Query Builder but before the result is returned in that builder method.
6 |
7 | We can use this oppurtunity to get things like the inserted ID.
8 |
9 | For the Postgres Post Processor we have a RETURNING * string in the insert so the result
10 | will already have the full inserted record in the results. Therefore, we can just return
11 | the results
12 | """
13 |
14 | def process_insert_get_id(self, builder, results, id_key):
15 | """Process the results from the query to the database.
16 |
17 | Args:
18 | builder (masoniteorm.builder.QueryBuilder): The query builder class
19 | results (dict): The result from an insert query or the creates from the query builder.
20 | This is usually a dictionary.
21 | id_key (string): The key to set the primary key to. This is usually the primary key of the table.
22 |
23 | Returns:
24 | dictionary: Should return the modified dictionary.
25 | """
26 |
27 | last_id = builder.new_connection().query(
28 | "SELECT @@Identity as [id]", results=1
29 | )
30 |
31 | id = last_id["id"]
32 |
33 | if str(id).isdigit():
34 | id = int(id)
35 | else:
36 | id = str(id)
37 |
38 | results.update({id_key: id})
39 | return results
40 |
41 | def get_column_value(self, builder, column, results, id_key, id_value):
42 | """Gets the specific column value from a table. Typically done after an update to
43 | refetch the new value of a field.
44 |
45 | builder (masoniteorm.builder.QueryBuilder): The query builder class
46 | column (string): The column to refetch the value for.
47 | results (dict): The result from an update query from the query builder.
48 | This is usually a dictionary.
49 | id_key (string): The key to fetch the primary key for. This is usually the primary key of the table.
50 | id_value (string): The value of the primary key to fetch
51 | """
52 |
53 | new_builder = builder.select(column)
54 | if id_key and id_value:
55 | new_builder.where(id_key, id_value)
56 | return new_builder.first()[column]
57 |
58 | return {}
59 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/processors/MySQLPostProcessor.py:
--------------------------------------------------------------------------------
1 | class MySQLPostProcessor:
2 | """Post processor classes are responsable for modifying the result after a query.
3 |
4 | Post Processors are called after the connection calls the database in the
5 | Query Builder but before the result is returned in that builder method.
6 |
7 | We can use this oppurtunity to get things like the inserted ID.
8 |
9 | For the SQLite Post Processor we have an attribute on the connection class we can use to fetch the ID.
10 | """
11 |
12 | def process_insert_get_id(self, builder, results, id_key):
13 | """Process the results from the query to the database.
14 |
15 | Args:
16 | builder (masoniteorm.builder.QueryBuilder): The query builder class
17 | results (dict): The result from an insert query or the creates from the query builder.
18 | This is usually a dictionary.
19 | id_key (string): The key to set the primary key to. This is usually the primary key of the table.
20 |
21 | Returns:
22 | dictionary: Should return the modified dictionary.
23 | """
24 |
25 | if id_key not in results:
26 | results.update({id_key: builder._connection.get_cursor().lastrowid})
27 | return results
28 |
29 | def get_column_value(self, builder, column, results, id_key, id_value):
30 | """Gets the specific column value from a table. Typically done after an update to
31 | refetch the new value of a field.
32 |
33 | builder (masoniteorm.builder.QueryBuilder): The query builder class
34 | column (string): The column to refetch the value for.
35 | results (dict): The result from an update query from the query builder.
36 | This is usually a dictionary.
37 | id_key (string): The key to fetch the primary key for. This is usually the primary key of the table.
38 | id_value (string): The value of the primary key to fetch
39 | """
40 |
41 | new_builder = builder.select(column)
42 | if id_key and id_value:
43 | new_builder.where(id_key, id_value)
44 | return new_builder.first()[column]
45 |
46 | return {}
47 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/processors/PostgresPostProcessor.py:
--------------------------------------------------------------------------------
1 | class PostgresPostProcessor:
2 | """Post processor classes are responsable for modifying the result after a query.
3 |
4 | Post Processors are called after the connection calls the database in the
5 | Query Builder but before the result is returned in that builder method.
6 |
7 | We can use this oppurtunity to get things like the inserted ID.
8 |
9 | For the Postgres Post Processor we have a RETURNING * string in the insert so the result
10 | will already have the full inserted record in the results. Therefore, we can just return
11 | the results
12 | """
13 |
14 | def process_insert_get_id(self, builder, results, id_key):
15 | """Process the results from the query to the database.
16 |
17 | Args:
18 | builder (masoniteorm.builder.QueryBuilder): The query builder class
19 | results (dict): The result from an insert query or the creates from the query builder.
20 | This is usually a dictionary.
21 | id_key (string): The key to set the primary key to. This is usually the primary key of the table.
22 |
23 | Returns:
24 | dictionary: Should return the modified dictionary.
25 | """
26 |
27 | return results
28 |
29 | def get_column_value(self, builder, column, results, id_key, id_value):
30 | """Gets the specific column value from a table. Typically done after an update to
31 | refetch the new value of a field.
32 |
33 | builder (masoniteorm.builder.QueryBuilder): The query builder class
34 | column (string): The column to refetch the value for.
35 | results (dict): The result from an update query from the query builder.
36 | This is usually a dictionary.
37 | id_key (string): The key to fetch the primary key for. This is usually the primary key of the table.
38 | id_value (string): The value of the primary key to fetch
39 | """
40 |
41 | if column in results:
42 | return results[column]
43 |
44 | new_builder = builder.select(column)
45 | if id_key and id_value:
46 | new_builder.where(id_key, id_value)
47 | return new_builder.first()[column]
48 |
49 | return {}
50 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/processors/SQLitePostProcessor.py:
--------------------------------------------------------------------------------
1 | class SQLitePostProcessor:
2 | """Post processor classes are responsable for modifying the result after a query.
3 |
4 | Post Processors are called after the connection calls the database in the
5 | Query Builder but before the result is returned in that builder method.
6 |
7 | We can use this oppurtunity to get things like the inserted ID.
8 |
9 | For the SQLite Post Processor we have an attribute on the connection class we can use to fetch the ID.
10 | """
11 |
12 | def process_insert_get_id(self, builder, results, id_key="id"):
13 | """Process the results from the query to the database.
14 |
15 | Args:
16 | builder (masoniteorm.builder.QueryBuilder): The query builder class
17 | results (dict): The result from an insert query or the creates from the query builder.
18 | This is usually a dictionary.
19 | id_key (string): The key to set the primary key to. This is usually the primary key of the table.
20 |
21 | Returns:
22 | dictionary: Should return the modified dictionary.
23 | """
24 |
25 | if id_key not in results:
26 | results.update({id_key: builder.get_connection().get_cursor().lastrowid})
27 |
28 | return results
29 |
30 | def get_column_value(self, builder, column, results, id_key, id_value):
31 | """Gets the specific column value from a table. Typically done after an update to
32 | refetch the new value of a field.
33 |
34 | builder (masoniteorm.builder.QueryBuilder): The query builder class
35 | column (string): The column to refetch the value for.
36 | results (dict): The result from an update query from the query builder.
37 | This is usually a dictionary.
38 | id_key (string): The key to fetch the primary key for. This is usually the primary key of the table.
39 | id_value (string): The value of the primary key to fetch
40 | """
41 |
42 | new_builder = builder.select(column)
43 | if id_key and id_value:
44 | new_builder.where(id_key, id_value)
45 | return new_builder.first()[column]
46 |
47 | return {}
48 |
--------------------------------------------------------------------------------
/src/masoniteorm/query/processors/__init__.py:
--------------------------------------------------------------------------------
1 | from .MSSQLPostProcessor import MSSQLPostProcessor
2 | from .MySQLPostProcessor import MySQLPostProcessor
3 | from .PostgresPostProcessor import PostgresPostProcessor
4 | from .SQLitePostProcessor import SQLitePostProcessor
5 |
--------------------------------------------------------------------------------
/src/masoniteorm/relationships/HasMany.py:
--------------------------------------------------------------------------------
1 | from ..collection import Collection
2 | from .BaseRelationship import BaseRelationship
3 |
4 |
5 | class HasMany(BaseRelationship):
6 | """Has Many Relationship Class."""
7 |
8 | def apply_query(self, foreign, owner):
9 | """Apply the query and return a dictionary to be hydrated
10 |
11 | Arguments:
12 | foreign {oject} -- The relationship object
13 | owner {object} -- The current model oject.
14 |
15 | Returns:
16 | dict -- A dictionary of data which will be hydrated.
17 | """
18 | result = foreign.where(
19 | self.foreign_key, owner.__attributes__[self.local_key]
20 | ).get()
21 |
22 | return result
23 |
24 | def set_keys(self, owner, attribute):
25 | self.local_key = self.local_key or "id"
26 | self.foreign_key = self.foreign_key or f"{attribute}_id"
27 | return self
28 |
29 | def register_related(self, key, model, collection):
30 | model.add_relation(
31 | {key: collection.get(getattr(model, self.local_key)) or Collection()}
32 | )
33 |
34 | def map_related(self, related_result):
35 | return related_result.group_by(self.foreign_key)
36 |
37 | def attach(self, current_model, related_record):
38 | local_key_value = getattr(current_model, self.local_key)
39 | if not related_record.is_created():
40 | related_record.fill({self.foreign_key: local_key_value})
41 | return related_record.create(related_record.all_attributes(), cast=True)
42 |
43 | return related_record.update({self.foreign_key: local_key_value})
44 |
45 | def get_related(self, query, relation, eagers=None, callback=None):
46 | eagers = eagers or []
47 | builder = self.get_builder().with_(eagers)
48 |
49 | if callback:
50 | callback(builder)
51 | if isinstance(relation, Collection):
52 | return builder.where_in(
53 | f"{builder.get_table_name()}.{self.foreign_key}",
54 | Collection(relation._get_value(self.local_key)).unique(),
55 | ).get()
56 |
57 | return builder.where(
58 | f"{builder.get_table_name()}.{self.foreign_key}",
59 | getattr(relation, self.local_key),
60 | ).get()
61 |
--------------------------------------------------------------------------------
/src/masoniteorm/relationships/HasOne.py:
--------------------------------------------------------------------------------
1 | from ..collection import Collection
2 | from .BaseRelationship import BaseRelationship
3 |
4 |
5 | class HasOne(BaseRelationship):
6 | """Belongs To Relationship Class."""
7 |
8 | def __init__(self, fn, foreign_key=None, local_key=None):
9 | if isinstance(fn, str):
10 | self.foreign_key = fn
11 | self.local_key = foreign_key or "id"
12 | else:
13 | self.fn = fn
14 | self.local_key = local_key or "id"
15 | self.foreign_key = foreign_key
16 |
17 | def set_keys(self, owner, attribute):
18 | self.local_key = self.local_key or "id"
19 | self.foreign_key = self.foreign_key or f"{attribute}_id"
20 | return self
21 |
22 | def apply_query(self, foreign, owner):
23 | """Apply the query and return a dictionary to be hydrated
24 |
25 | Arguments:
26 | foreign {oject} -- The relationship object
27 | owner {object} -- The current model oject.
28 |
29 | Returns:
30 | dict -- A dictionary of data which will be hydrated.
31 | """
32 |
33 | return foreign.where(
34 | self.foreign_key, owner.__attributes__[self.local_key]
35 | ).first()
36 |
37 | def get_related(self, query, relation, eagers=(), callback=None):
38 | """Gets the relation needed between the relation and the related builder. If the relation is a collection
39 | then will need to pluck out all the keys from the collection and fetch from the related builder. If
40 | relation is just a Model then we can just call the model based on the value of the related
41 | builders primary key.
42 |
43 | Args:
44 | relation (Model|Collection):
45 |
46 | Returns:
47 | Model|Collection
48 | """
49 | builder = self.get_builder().with_(eagers)
50 |
51 | if callback:
52 | callback(builder)
53 |
54 | if isinstance(relation, Collection):
55 | return builder.where_in(
56 | f"{builder.get_table_name()}.{self.foreign_key}",
57 | Collection(relation._get_value(self.local_key)).unique(),
58 | ).get()
59 | else:
60 | return builder.where(
61 | f"{builder.get_table_name()}.{self.foreign_key}",
62 | getattr(relation, self.local_key),
63 | ).first()
64 |
65 | def query_has(self, current_query_builder, method="where_exists"):
66 | related_builder = self.get_builder()
67 |
68 | getattr(current_query_builder, method)(
69 | related_builder.where_column(
70 | f"{related_builder.get_table_name()}.{self.foreign_key}",
71 | f"{current_query_builder.get_table_name()}.{self.local_key}",
72 | )
73 | )
74 |
75 | return related_builder
76 |
77 | def query_where_exists(self, builder, callback, method="where_exists"):
78 | query = self.get_builder()
79 | getattr(builder, method)(
80 | callback(
81 | query.where_column(
82 | f"{query.get_table_name()}.{self.foreign_key}",
83 | f"{builder.get_table_name()}.{self.local_key}",
84 | )
85 | )
86 | )
87 | return query
88 |
89 | def register_related(self, key, model, collection):
90 | related = collection.where(
91 | self.foreign_key, getattr(model, self.local_key)
92 | ).first()
93 |
94 | model.add_relation({key: related or None})
95 |
96 | def map_related(self, related_result):
97 | return related_result
98 |
99 | def attach(self, current_model, related_record):
100 | local_key_value = getattr(current_model, self.local_key)
101 | if not related_record.is_created():
102 | related_record.fill({self.foreign_key: local_key_value})
103 | return related_record.create(related_record.all_attributes(), cast=True)
104 |
105 | return related_record.update({self.foreign_key: local_key_value})
106 |
107 | def detach(self, current_model, related_record):
108 | return related_record.update({self.foreign_key: None})
109 |
--------------------------------------------------------------------------------
/src/masoniteorm/relationships/MorphTo.py:
--------------------------------------------------------------------------------
1 | from ..collection import Collection
2 | from ..config import load_config
3 | from .BaseRelationship import BaseRelationship
4 |
5 |
6 | class MorphTo(BaseRelationship):
7 | def __init__(self, fn, morph_key="record_type", morph_id="record_id"):
8 | if isinstance(fn, str):
9 | self.fn = None
10 | self.morph_key = fn
11 | self.morph_id = morph_key
12 | else:
13 | self.fn = fn
14 | self.morph_id = morph_id
15 | self.morph_key = morph_key
16 |
17 | def get_builder(self):
18 | return self._related_builder
19 |
20 | def set_keys(self, owner, attribute):
21 | self.morph_id = self.morph_id or "record_id"
22 | self.morph_key = self.morph_key or "record_type"
23 | return self
24 |
25 | def __get__(self, instance, owner):
26 | """This method is called when the decorated method is accessed.
27 |
28 | Arguments:
29 | instance {object|None} -- The instance we called.
30 | If we didn't call the attribute and only accessed it then this will be None.
31 |
32 | owner {object} -- The current model that the property was accessed on.
33 |
34 | Returns:
35 | object -- Either returns a builder or a hydrated model.
36 | """
37 | attribute = self.fn.__name__
38 | self._related_builder = instance.builder
39 | self.set_keys(owner, self.fn)
40 |
41 | if not instance.is_loaded():
42 | return self
43 |
44 | if attribute in instance._relationships:
45 | return instance._relationships[attribute]
46 |
47 | return self.apply_query(self._related_builder, instance)
48 |
49 | def __getattr__(self, attribute):
50 | relationship = self.fn(self)()
51 | return getattr(relationship._related_builder, attribute)
52 |
53 | def apply_query(self, builder, instance):
54 | """Apply the query and return a dictionary to be hydrated
55 |
56 | Arguments:
57 | builder {oject} -- The relationship object
58 | instance {object} -- The current model oject.
59 |
60 | Returns:
61 | dict -- A dictionary of data which will be hydrated.
62 | """
63 | model = self.morph_map().get(instance.__attributes__[self.morph_key])
64 | record = instance.__attributes__[self.morph_id]
65 |
66 | return model.where(model.get_primary_key(), record).first()
67 |
68 | def get_related(self, query, relation, eagers=None, callback=None):
69 | """Gets the relation needed between the relation and the related builder. If the relation is a collection
70 | then will need to pluck out all the keys from the collection and fetch from the related builder. If
71 | relation is just a Model then we can just call the model based on the value of the related
72 | builders primary key.
73 |
74 | Args:
75 | relation (Model|Collection):
76 |
77 | Returns:
78 | Model|Collection
79 | """
80 | if isinstance(relation, Collection):
81 | relations = Collection()
82 | for group, items in relation.group_by(self.morph_key).items():
83 | morphed_model = self.morph_map().get(group)
84 | relations.merge(
85 | morphed_model.where_in(
86 | f"{morphed_model.get_table_name()}.{morphed_model.get_primary_key()}",
87 | Collection(items)
88 | .pluck(self.morph_id, keep_nulls=False)
89 | .unique(),
90 | ).get()
91 | )
92 | return relations
93 | else:
94 | model = self.morph_map().get(getattr(relation, self.morph_key))
95 | if model:
96 | return model.find(getattr(relation, self.morph_id))
97 |
98 | def register_related(self, key, model, collection):
99 | morphed_model = self.morph_map().get(getattr(model, self.morph_key))
100 |
101 | related = collection.where(
102 | morphed_model.get_primary_key(), getattr(model, self.morph_id)
103 | ).first()
104 |
105 | model.add_relation({key: related})
106 |
107 | def morph_map(self):
108 | return load_config().DB._morph_map
109 |
110 | def map_related(self, related_result):
111 | return related_result
112 |
--------------------------------------------------------------------------------
/src/masoniteorm/relationships/MorphToMany.py:
--------------------------------------------------------------------------------
1 | from ..collection import Collection
2 | from ..config import load_config
3 | from .BaseRelationship import BaseRelationship
4 |
5 |
6 | class MorphToMany(BaseRelationship):
7 | def __init__(self, fn, morph_key="record_type", morph_id="record_id"):
8 | if isinstance(fn, str):
9 | self.fn = None
10 | self.morph_key = fn
11 | self.morph_id = morph_key
12 | else:
13 | self.fn = fn
14 | self.morph_id = morph_id
15 | self.morph_key = morph_key
16 |
17 | def get_builder(self):
18 | return self._related_builder
19 |
20 | def set_keys(self, owner, attribute):
21 | self.morph_id = self.morph_id or "record_id"
22 | self.morph_key = self.morph_key or "record_type"
23 | return self
24 |
25 | def __get__(self, instance, owner):
26 | """This method is called when the decorated method is accessed.
27 |
28 | Arguments:
29 | instance {object|None} -- The instance we called.
30 | If we didn't call the attribute and only accessed it then this will be None.
31 |
32 | owner {object} -- The current model that the property was accessed on.
33 |
34 | Returns:
35 | object -- Either returns a builder or a hydrated model.
36 | """
37 | attribute = self.fn.__name__
38 | self._related_builder = instance.builder
39 | self.set_keys(owner, self.fn)
40 |
41 | if not instance.is_loaded():
42 | return self
43 |
44 | if attribute in instance._relationships:
45 | return instance._relationships[attribute]
46 |
47 | return self.apply_query(self._related_builder, instance)
48 |
49 | def __getattr__(self, attribute):
50 | relationship = self.fn(self)()
51 | return getattr(relationship.builder, attribute)
52 |
53 | def apply_query(self, builder, instance):
54 | """Apply the query and return a dictionary to be hydrated
55 |
56 | Arguments:
57 | builder {oject} -- The relationship object
58 | instance {object} -- The current model oject.
59 |
60 | Returns:
61 | dict -- A dictionary of data which will be hydrated.
62 | """
63 | model = self.morph_map().get(instance.__attributes__[self.morph_key])
64 | record = instance.__attributes__[self.morph_id]
65 |
66 | return model.where(model.get_primary_key(), record).first()
67 |
68 | def get_related(self, query, relation, eagers=None, callback=None):
69 | """Gets the relation needed between the relation and the related builder. If the relation is a collection
70 | then will need to pluck out all the keys from the collection and fetch from the related builder. If
71 | relation is just a Model then we can just call the model based on the value of the related
72 | builders primary key.
73 |
74 | Args:
75 | relation (Model|Collection):
76 |
77 | Returns:
78 | Model|Collection
79 | """
80 | if isinstance(relation, Collection):
81 | relations = Collection()
82 | for group, items in relation.group_by(self.morph_key).items():
83 | morphed_model = self.morph_map().get(group)
84 | relations.merge(
85 | morphed_model.where_in(
86 | f"{morphed_model.get_table_name()}.{morphed_model.get_primary_key()}",
87 | Collection(items)
88 | .pluck(self.morph_id, keep_nulls=False)
89 | .unique(),
90 | ).get()
91 | )
92 | return relations
93 | else:
94 | model = self.morph_map().get(getattr(relation, self.morph_key))
95 | if model:
96 | return model.find([getattr(relation, self.morph_id)])
97 |
98 | def register_related(self, key, model, collection):
99 | morphed_model = self.morph_map().get(getattr(model, self.morph_key))
100 |
101 | related = collection.where(
102 | morphed_model.get_primary_key(), getattr(model, self.morph_id)
103 | )
104 |
105 | model.add_relation({key: related})
106 |
107 | def morph_map(self):
108 | return load_config().DB._morph_map
109 |
--------------------------------------------------------------------------------
/src/masoniteorm/relationships/__init__.py:
--------------------------------------------------------------------------------
1 | from .BelongsTo import BelongsTo as belongs_to
2 | from .BelongsToMany import BelongsToMany as belongs_to_many
3 | from .HasMany import HasMany as has_many
4 | from .HasManyThrough import HasManyThrough as has_many_through
5 | from .HasOne import HasOne as has_one
6 | from .HasOneThrough import HasOneThrough as has_one_through
7 | from .MorphMany import MorphMany as morph_many
8 | from .MorphOne import MorphOne as morph_one
9 | from .MorphTo import MorphTo as morph_to
10 | from .MorphToMany import MorphToMany as morph_to_many
11 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/Column.py:
--------------------------------------------------------------------------------
1 | class Column:
2 | """Used for creating or modifying columns."""
3 |
4 | def __init__(
5 | self,
6 | name,
7 | column_type,
8 | length=None,
9 | values=None,
10 | nullable=False,
11 | default=None,
12 | signed=None,
13 | default_is_raw=False,
14 | column_python_type=str,
15 | ):
16 | self.column_type = column_type
17 | self.column_python_type = column_python_type
18 | self.name = name
19 | self.length = length
20 | self.values = values or []
21 | self.is_null = nullable
22 | self._after = None
23 | self.old_column = ""
24 | self.default = default
25 | self._signed = signed
26 | self.default_is_raw = default_is_raw
27 | self.primary = False
28 | self.comment = None
29 |
30 | def nullable(self):
31 | """Sets this column to be nullable
32 |
33 | Returns:
34 | self
35 | """
36 | self.is_null = True
37 | return self
38 |
39 | def signed(self):
40 | """Sets this column to be nullable
41 |
42 | Returns:
43 | self
44 | """
45 | self._signed = "signed"
46 | return self
47 |
48 | def unsigned(self):
49 | """Sets this column to be nullable
50 |
51 | Returns:
52 | self
53 | """
54 | self._signed = "unsigned"
55 | return self
56 |
57 | def not_nullable(self):
58 | """Sets this column to be not nullable
59 |
60 | Returns:
61 | self
62 | """
63 | self.is_null = False
64 | return self
65 |
66 | def set_as_primary(self):
67 | self.primary = True
68 |
69 | def rename(self, column):
70 | """Renames this column to a new name
71 |
72 | Arguments:
73 | column {string} -- The old column name
74 |
75 | Returns:
76 | self
77 | """
78 | self.old_column = column
79 | return self
80 |
81 | def after(self, after):
82 | """Sets the column that this new column should be created after.
83 |
84 | This is useful for setting the location of the new column in the table schema.
85 |
86 | Arguments:
87 | after {string} -- The column that this new column should be created after
88 |
89 | Returns:
90 | self
91 | """
92 | self._after = after
93 | return self
94 |
95 | def get_after_column(self):
96 | """Sets the column that this new column should be created after.
97 |
98 | This is useful for setting the location of the new column in the table schema.
99 |
100 | Arguments:
101 | after {string} -- The column that this new column should be created after
102 |
103 | Returns:
104 | self
105 | """
106 | return self._after
107 |
108 | def default(self, value, raw=False):
109 | """Sets a default value for this column
110 |
111 | Arguments:
112 | value {string} -- A default value.
113 | raw {bool} -- should the value be quoted
114 |
115 | Returns:
116 | self
117 | """
118 | self.default = value
119 | self.default_is_raw = raw
120 | return self
121 |
122 | def change(self):
123 | """Sets the schema to create a modify sql statement.
124 |
125 | Returns:
126 | self
127 | """
128 | self._action = "modify"
129 | return self
130 |
131 | def use_current(self):
132 | """Sets the column to use a current timestamp.
133 |
134 | Used for timestamp columns.
135 |
136 | Returns:
137 | self
138 | """
139 | self.default = "current"
140 | return self
141 |
142 | def add_comment(self, comment):
143 | self.comment = comment
144 | return self
145 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/ColumnDiff.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/src/masoniteorm/schema/ColumnDiff.py
--------------------------------------------------------------------------------
/src/masoniteorm/schema/Constraint.py:
--------------------------------------------------------------------------------
1 | class Constraint:
2 | def __init__(self, name, constraint_type, columns=None):
3 | self.name = name
4 | self.constraint_type = constraint_type
5 | self.columns = columns or []
6 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/ForeignKeyConstraint.py:
--------------------------------------------------------------------------------
1 | class ForeignKeyConstraint:
2 | def __init__(self, column, foreign_table, foreign_column, name=None):
3 | self.column = column
4 | self.foreign_table = foreign_table
5 | self.foreign_column = foreign_column
6 | self.delete_action = None
7 | self.update_action = None
8 | self.constraint_name = name
9 |
10 | def references(self, foreign_column):
11 | self.foreign_column = foreign_column
12 | return self
13 |
14 | def on(self, foreign_table):
15 | self.foreign_table = foreign_table
16 | return self
17 |
18 | def on_delete(self, action):
19 | self.delete_action = action
20 | return self
21 |
22 | def on_update(self, action):
23 | self.update_action = action
24 | return self
25 |
26 | def name(self, name):
27 | self.constraint_name = name
28 | return self
29 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/Index.py:
--------------------------------------------------------------------------------
1 | class Index:
2 | def __init__(self, column, name, index_type):
3 | self.column = column
4 | self.name = name
5 | self.index_type = index_type
6 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/Table.py:
--------------------------------------------------------------------------------
1 | from .Column import Column
2 | from .Constraint import Constraint
3 | from .Index import Index
4 | from .ForeignKeyConstraint import ForeignKeyConstraint
5 |
6 |
7 | class Table:
8 | def __init__(self, table):
9 | self.name = table
10 | self.added_columns = {}
11 | self.added_constraints = {}
12 | self.added_indexes = {}
13 | self.added_foreign_keys = {}
14 | self.renamed_columns = {}
15 | self.drop_indexes = {}
16 | self.foreign_keys = {}
17 | self.primary_key = None
18 | self.comment = None
19 |
20 | def add_column(
21 | self,
22 | name=None,
23 | column_type=None,
24 | length=None,
25 | values=None,
26 | nullable=False,
27 | default=None,
28 | signed=None,
29 | default_is_raw=False,
30 | primary=False,
31 | column_python_type=str,
32 | ):
33 | column = Column(
34 | name,
35 | column_type,
36 | length=length,
37 | nullable=nullable,
38 | values=values or [],
39 | default=default,
40 | signed=signed,
41 | default_is_raw=default_is_raw,
42 | column_python_type=column_python_type,
43 | )
44 | if primary:
45 | column.set_as_primary()
46 | self.added_columns.update({name: column})
47 | return column
48 |
49 | def add_constraint(self, name, constraint_type, columns=None):
50 | self.added_constraints.update(
51 | {name: Constraint(name, constraint_type, columns=columns or [])}
52 | )
53 |
54 | def add_foreign_key(self, column, table=None, foreign_column=None, name=None):
55 | foreign_key = ForeignKeyConstraint(
56 | column, table, foreign_column, name=name or f"{self.name}_{column}_foreign"
57 | )
58 | self.added_foreign_keys.update({column: foreign_key})
59 |
60 | return foreign_key
61 |
62 | def get_added_foreign_keys(self):
63 | return self.added_foreign_keys
64 |
65 | def get_constraint(self, name):
66 | return self.added_constraints[name]
67 |
68 | def get_added_constraints(self):
69 | return self.added_constraints
70 |
71 | def get_added_columns(self):
72 | return self.added_columns
73 |
74 | def get_renamed_columns(self):
75 | return self.added_columns
76 |
77 | def set_primary_key(self, columns):
78 | self.primary_key = columns
79 | return self
80 |
81 | def add_index(self, column, name, index_type):
82 | self.added_indexes.update({name: Index(column, name, index_type)})
83 |
84 | def get_index(self, name):
85 | return self.added_indexes[name]
86 |
87 | def add_comment(self, comment):
88 | self.comment = comment
89 | return self
90 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/TableDiff.py:
--------------------------------------------------------------------------------
1 | from .Column import Column
2 | from .Table import Table
3 |
4 |
5 | class TableDiff(Table):
6 | def __init__(self, name):
7 | self.name = name
8 | self.from_table = None
9 | self.new_name = None
10 | self.removed_indexes = []
11 | self.removed_unique_indexes = []
12 | self.added_indexes = {}
13 | self.added_columns = {}
14 | self.changed_columns = {}
15 | self.dropped_columns = []
16 | self.dropped_foreign_keys = []
17 | self.dropped_primary_keys = []
18 | self.renamed_columns = {}
19 | self.removed_constraints = {}
20 | self.added_constraints = {}
21 | self.added_foreign_keys = {}
22 | self.comment = None
23 |
24 | def remove_constraint(self, name):
25 | self.removed_constraints.update({name: self.from_table.get_constraint(name)})
26 |
27 | def get_removed_constraints(self):
28 | return self.removed_constraints
29 |
30 | def get_renamed_columns(self):
31 | return self.renamed_columns
32 |
33 | def rename_column(
34 | self,
35 | original_name,
36 | new_name,
37 | column_type=None,
38 | length=None,
39 | nullable=False,
40 | default=None,
41 | ):
42 | self.renamed_columns.update(
43 | {
44 | original_name: Column(
45 | new_name,
46 | column_type,
47 | length=length,
48 | nullable=nullable,
49 | default=default,
50 | )
51 | }
52 | )
53 |
54 | def remove_index(self, name):
55 | self.removed_indexes.append(name)
56 |
57 | def remove_unique_index(self, name):
58 | self.removed_unique_indexes.append(name)
59 |
60 | def drop_column(self, name):
61 | self.dropped_columns.append(name)
62 |
63 | def get_dropped_columns(self):
64 | return self.dropped_columns
65 |
66 | def get_dropped_foreign_keys(self):
67 | return self.dropped_foreign_keys
68 |
69 | def drop_foreign(self, name):
70 | self.dropped_foreign_keys.append(name)
71 | return self
72 |
73 | def drop_primary(self, name):
74 | self.dropped_primary_keys.append(name)
75 | return self
76 |
77 | def change_column(self, added_column):
78 | self.added_columns.pop(added_column.name)
79 |
80 | self.changed_columns.update({added_column.name: added_column})
81 |
82 | def add_comment(self, comment):
83 | self.comment = comment
84 | return self
85 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/__init__.py:
--------------------------------------------------------------------------------
1 | from .Schema import Schema
2 | from .Table import Table
3 | from .Column import Column
4 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/platforms/Platform.py:
--------------------------------------------------------------------------------
1 | class Platform:
2 | foreign_key_actions = {
3 | "cascade": "CASCADE",
4 | "set null": "SET NULL",
5 | "cascade": "CASCADE",
6 | "restrict": "RESTRICT",
7 | "no action": "NO ACTION",
8 | "default": "SET DEFAULT",
9 | }
10 |
11 | signed = {"signed": "SIGNED", "unsigned": "UNSIGNED"}
12 |
13 | def columnize(self, columns):
14 | sql = []
15 | for name, column in columns.items():
16 | if column.length:
17 | length = self.create_column_length(column.column_type).format(
18 | length=column.length
19 | )
20 | else:
21 | length = ""
22 |
23 | if column.default in (0,):
24 | default = f" DEFAULT {column.default}"
25 | elif column.default in self.premapped_defaults.keys():
26 | default = self.premapped_defaults.get(column.default)
27 | elif column.default:
28 | if isinstance(column.default, (str,)) and not column.default_is_raw:
29 | default = f" DEFAULT '{column.default}'"
30 | else:
31 | default = f" DEFAULT {column.default}"
32 | else:
33 | default = ""
34 |
35 | sql.append(
36 | self.columnize_string()
37 | .format(
38 | name=column.name,
39 | data_type=self.type_map.get(column.column_type, ""),
40 | length=length,
41 | constraint="PRIMARY KEY" if column.primary else "",
42 | nullable=self.premapped_nulls.get(column.is_null) or "",
43 | default=default,
44 | )
45 | .strip()
46 | )
47 |
48 | return sql
49 |
50 | def columnize_string(self):
51 | raise NotImplementedError
52 |
53 | def create_column_length(self, column_type):
54 | if column_type in self.types_without_lengths:
55 | return ""
56 | return "({length})"
57 |
58 | def foreign_key_constraintize(self, table, foreign_keys):
59 | sql = []
60 | for name, foreign_key in foreign_keys.items():
61 | cascade = ""
62 | if foreign_key.delete_action:
63 | cascade += f" ON DELETE {self.foreign_key_actions.get(foreign_key.delete_action.lower())}"
64 | if foreign_key.update_action:
65 | cascade += f" ON UPDATE {self.foreign_key_actions.get(foreign_key.update_action.lower())}"
66 | sql.append(
67 | self.get_foreign_key_constraint_string().format(
68 | column=self.wrap_column(foreign_key.column),
69 | constraint_name=foreign_key.constraint_name,
70 | table=self.wrap_table(table),
71 | foreign_table=self.wrap_table(foreign_key.foreign_table),
72 | foreign_column=self.wrap_column(foreign_key.foreign_column),
73 | cascade=cascade,
74 | )
75 | )
76 | return sql
77 |
78 | def constraintize(self, constraints):
79 | sql = []
80 | for name, constraint in constraints.items():
81 | sql.append(
82 | getattr(
83 | self, f"get_{constraint.constraint_type}_constraint_string"
84 | )().format(columns=", ".join(constraint.columns))
85 | )
86 | return sql
87 |
88 | def wrap_table(self, table_name):
89 | return self.get_table_string().format(table=table_name)
90 |
91 | def wrap_column(self, column_name):
92 | return self.get_column_string().format(column=column_name)
93 |
--------------------------------------------------------------------------------
/src/masoniteorm/schema/platforms/__init__.py:
--------------------------------------------------------------------------------
1 | from .SQLitePlatform import SQLitePlatform
2 | from .MySQLPlatform import MySQLPlatform
3 | from .MSSQLPlatform import MSSQLPlatform
4 | from .PostgresPlatform import PostgresPlatform
5 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/BaseScope.py:
--------------------------------------------------------------------------------
1 | class BaseScope:
2 | def on_boot(self, builder):
3 | raise NotImplementedError()
4 |
5 | def on_remove(self, builder):
6 | raise NotImplementedError()
7 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/SoftDeleteScope.py:
--------------------------------------------------------------------------------
1 | from .BaseScope import BaseScope
2 |
3 |
4 | class SoftDeleteScope(BaseScope):
5 | """Global scope class to add soft deleting to models."""
6 |
7 | def __init__(self, deleted_at_column="deleted_at"):
8 | self.deleted_at_column = deleted_at_column
9 |
10 | def on_boot(self, builder):
11 | builder.set_global_scope("_where_null", self._where_null, action="select")
12 | builder.set_global_scope(
13 | "_query_set_null_on_delete", self._query_set_null_on_delete, action="delete"
14 | )
15 | builder.macro("with_trashed", self._with_trashed)
16 | builder.macro("only_trashed", self._only_trashed)
17 | builder.macro("force_delete", self._force_delete)
18 | builder.macro("restore", self._restore)
19 |
20 | def on_remove(self, builder):
21 | builder.remove_global_scope("_where_null", action="select")
22 | builder.remove_global_scope("_query_set_null_on_delete", action="delete")
23 |
24 | def _where_null(self, builder):
25 | return builder.where_null(
26 | f"{builder.get_table_name()}.{self.deleted_at_column}"
27 | )
28 |
29 | def _with_trashed(self, model, builder):
30 | builder.remove_global_scope("_where_null", action="select")
31 | return builder
32 |
33 | def _only_trashed(self, model, builder):
34 | builder.remove_global_scope("_where_null", action="select")
35 | return builder.where_not_null(self.deleted_at_column)
36 |
37 | def _force_delete(self, model, builder, query=False):
38 | if query:
39 | return builder.remove_global_scope(self).set_action("delete")
40 | return builder.remove_global_scope(self).delete()
41 |
42 | def _restore(self, model, builder):
43 | return builder.remove_global_scope(self).update({self.deleted_at_column: None})
44 |
45 | def _query_set_null_on_delete(self, builder):
46 | return builder.set_action("update").set_updates(
47 | {self.deleted_at_column: builder._model.get_new_datetime_string()}
48 | )
49 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/SoftDeletesMixin.py:
--------------------------------------------------------------------------------
1 | from .SoftDeleteScope import SoftDeleteScope
2 |
3 |
4 | class SoftDeletesMixin:
5 | """Global scope class to add soft deleting to models."""
6 |
7 | __deleted_at__ = "deleted_at"
8 |
9 | def boot_SoftDeletesMixin(self, builder):
10 | builder.set_global_scope(SoftDeleteScope(self.__deleted_at__))
11 |
12 | def get_deleted_at_column(self):
13 | return self.__deleted_at__
14 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/TimeStampsMixin.py:
--------------------------------------------------------------------------------
1 | from .TimeStampsScope import TimeStampsScope
2 |
3 |
4 | class TimeStampsMixin:
5 | """Global scope class to add soft deleting to models."""
6 |
7 | def boot_TimeStampsMixin(self, builder):
8 | builder.set_global_scope(TimeStampsScope())
9 |
10 | def activate_timestamps(self, boolean=True):
11 | self.__timestamps__ = boolean
12 | return self
13 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/TimeStampsScope.py:
--------------------------------------------------------------------------------
1 | from ..expressions.expressions import UpdateQueryExpression
2 | from .BaseScope import BaseScope
3 |
4 |
5 | class TimeStampsScope(BaseScope):
6 | """Global scope class to add soft deleting to models."""
7 |
8 | def on_boot(self, builder):
9 | builder.set_global_scope(
10 | "_timestamps", self.set_timestamp_create, action="insert"
11 | )
12 |
13 | builder.set_global_scope(
14 | "_timestamp_update", self.set_timestamp_update, action="update"
15 | )
16 |
17 | def on_remove(self, builder):
18 | pass
19 |
20 | def set_timestamp(owner_cls, query):
21 | owner_cls.updated_at = "now"
22 |
23 | def set_timestamp_create(self, builder):
24 | if not builder._model.__timestamps__:
25 | return builder
26 |
27 | builder._creates.update(
28 | {
29 | builder._model.date_updated_at: builder._model.get_new_date().to_datetime_string(),
30 | builder._model.date_created_at: builder._model.get_new_date().to_datetime_string(),
31 | }
32 | )
33 |
34 | def set_timestamp_update(self, builder):
35 | if not builder._model.__timestamps__:
36 | return builder
37 |
38 | for update in builder._updates:
39 | if builder._model.date_updated_at in update.column:
40 | return
41 | builder._updates += (
42 | UpdateQueryExpression(
43 | {
44 | builder._model.date_updated_at: builder._model.get_new_date().to_datetime_string()
45 | }
46 | ),
47 | )
48 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/UUIDPrimaryKeyMixin.py:
--------------------------------------------------------------------------------
1 | from .UUIDPrimaryKeyScope import UUIDPrimaryKeyScope
2 |
3 |
4 | class UUIDPrimaryKeyMixin:
5 | """Global scope class to add UUID as primary key to models."""
6 |
7 | def boot_UUIDPrimaryKeyMixin(self, builder):
8 | builder.set_global_scope(UUIDPrimaryKeyScope())
9 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/UUIDPrimaryKeyScope.py:
--------------------------------------------------------------------------------
1 | import uuid
2 |
3 | from .BaseScope import BaseScope
4 |
5 |
6 | class UUIDPrimaryKeyScope(BaseScope):
7 | """Global scope class to use UUID4 as primary key."""
8 |
9 | def on_boot(self, builder):
10 | builder.set_global_scope(
11 | "_UUID_primary_key", self.set_uuid_create, action="insert"
12 | )
13 | builder.set_global_scope(
14 | "_UUID_primary_key", self.set_bulk_uuid_create, action="bulk_create"
15 | )
16 |
17 | def on_remove(self, builder):
18 | pass
19 |
20 | def generate_uuid(self, builder, uuid_version, bytes=False):
21 | # UUID 3 and 5 requires parameters
22 | uuid_func = getattr(uuid, f"uuid{uuid_version}")
23 | args = []
24 | if uuid_version in [3, 5]:
25 | args = [builder._model.__uuid_namespace__, builder._model.__uuid_name__]
26 |
27 | return uuid_func(*args).bytes if bytes else str(uuid_func(*args))
28 |
29 | def build_uuid_pk(self, builder):
30 | uuid_version = getattr(builder._model, "__uuid_version__", 4)
31 | uuid_bytes = getattr(builder._model, "__uuid_bytes__", False)
32 | return {
33 | builder._model.__primary_key__: self.generate_uuid(
34 | builder, uuid_version, uuid_bytes
35 | )
36 | }
37 |
38 | def set_uuid_create(self, builder):
39 | # if there is already a primary key, no need to set a new one
40 | if builder._model.__primary_key__ not in builder._creates:
41 | builder._creates.update(self.build_uuid_pk(builder))
42 |
43 | def set_bulk_uuid_create(self, builder):
44 | for idx, create_atts in enumerate(builder._creates):
45 | if builder._model.__primary_key__ not in create_atts:
46 | builder._creates[idx].update(self.build_uuid_pk(builder))
47 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/__init__.py:
--------------------------------------------------------------------------------
1 | from .scope import scope
2 | from .BaseScope import BaseScope
3 | from .SoftDeletesMixin import SoftDeletesMixin
4 | from .SoftDeleteScope import SoftDeleteScope
5 | from .TimeStampsMixin import TimeStampsMixin
6 | from .TimeStampsScope import TimeStampsScope
7 | from .UUIDPrimaryKeyScope import UUIDPrimaryKeyScope
8 | from .UUIDPrimaryKeyMixin import UUIDPrimaryKeyMixin
9 |
--------------------------------------------------------------------------------
/src/masoniteorm/scopes/scope.py:
--------------------------------------------------------------------------------
1 | class scope:
2 | def __init__(self, callback, *params, **kwargs):
3 | self.fn = callback
4 |
5 | def __set_name__(self, cls, name):
6 | if cls not in cls._scopes:
7 | cls._scopes[cls] = {name: self.fn}
8 | else:
9 | cls._scopes[cls].update({name: self.fn})
10 | self.cls = cls
11 |
12 | def __call__(self, *args, **kwargs):
13 | instantiated = self.cls()
14 | builder = instantiated.get_builder()
15 | return self.fn(instantiated, builder, *args, **kwargs)
16 |
--------------------------------------------------------------------------------
/src/masoniteorm/seeds/Seeder.py:
--------------------------------------------------------------------------------
1 | import pydoc
2 |
3 |
4 | class Seeder:
5 | def __init__(self, dry=False, seed_path="databases/seeds", connection=None):
6 | self.ran_seeds = []
7 | self.dry = dry
8 | self.seed_path = seed_path
9 | self.connection = connection
10 | self.seed_module = seed_path.replace("/", ".").replace("\\", ".")
11 |
12 | def call(self, *seeder_classes):
13 | for seeder_class in seeder_classes:
14 | self.ran_seeds.append(seeder_class)
15 | if not self.dry:
16 | seeder_class(connection=self.connection).run()
17 |
18 | def run_database_seed(self):
19 | database_seeder = pydoc.locate(
20 | f"{self.seed_module}.database_seeder.DatabaseSeeder"
21 | )
22 |
23 | self.ran_seeds.append(database_seeder)
24 |
25 | if not self.dry:
26 | database_seeder(connection=self.connection).run()
27 |
28 | def run_specific_seed(self, seed):
29 | file_name = f"{self.seed_module}.{seed}"
30 | database_seeder = pydoc.locate(file_name)
31 |
32 | if not database_seeder:
33 | raise ValueError(f"Could not find the {file_name} seeder file")
34 |
35 | self.ran_seeds.append(database_seeder)
36 |
37 | if not self.dry:
38 | database_seeder(connection=self.connection).run()
39 | else:
40 | print(f"Running {database_seeder}")
41 |
--------------------------------------------------------------------------------
/src/masoniteorm/seeds/__init__.py:
--------------------------------------------------------------------------------
1 | from .Seeder import Seeder
2 |
--------------------------------------------------------------------------------
/src/masoniteorm/stubs/create-migration.html:
--------------------------------------------------------------------------------
1 | from masoniteorm.migrations import Migration
2 |
3 |
4 | class CreateUsersTable(Migration):
5 |
6 | def up(self):
7 | """Run the migrations."""
8 | with self.schema.create('users') as table:
9 | pass
10 |
11 | def down(self):
12 | """Revert the migrations."""
13 | self.schema.drop('users')
14 |
--------------------------------------------------------------------------------
/src/masoniteorm/stubs/table-migration.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/src/masoniteorm/stubs/table-migration.html
--------------------------------------------------------------------------------
/src/masoniteorm/testing/__init__.py:
--------------------------------------------------------------------------------
1 | from .BaseTestCaseSelectGrammar import BaseTestCaseSelectGrammar
2 |
--------------------------------------------------------------------------------
/tests/User.py:
--------------------------------------------------------------------------------
1 | """ User Model """
2 |
3 | from src.masoniteorm import Model
4 |
5 |
6 | class User(Model):
7 | """User Model"""
8 |
9 | __fillable__ = ["name", "email", "password"]
10 |
11 | __connection__ = "t"
12 |
13 | __auth__ = "email"
14 |
15 | @property
16 | def meta(self):
17 | return 1
18 |
--------------------------------------------------------------------------------
/tests/commands/test_shell.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from cleo import CommandTester
3 |
4 | from src.masoniteorm.commands import ShellCommand
5 |
6 |
7 | class TestShellCommand(unittest.TestCase):
8 | def setUp(self):
9 | self.command = ShellCommand()
10 | self.command_tester = CommandTester(self.command)
11 |
12 | def test_for_mysql(self):
13 | config = {
14 | "host": "localhost",
15 | "database": "orm",
16 | "user": "root",
17 | "port": "1234",
18 | "password": "secret",
19 | "prefix": "",
20 | "options": {"charset": "utf8mb4"},
21 | "full_details": {"driver": "mysql"},
22 | }
23 | command, _ = self.command.get_command(config)
24 | assert (
25 | command
26 | == "mysql orm --host localhost --port 1234 --user root --password secret --default-character-set utf8mb4"
27 | )
28 |
29 | def test_for_postgres(self):
30 | config = {
31 | "host": "localhost",
32 | "database": "orm",
33 | "user": "root",
34 | "port": "1234",
35 | "password": "secretpostgres",
36 | "prefix": "",
37 | "options": {"charset": "utf8mb4"},
38 | "full_details": {"driver": "postgres"},
39 | }
40 | command, env = self.command.get_command(config)
41 | assert command == "psql orm --host localhost --port 1234 --username root"
42 | assert env.get("PGPASSWORD", "secretpostgres")
43 |
44 | def test_for_sqlite(self):
45 | config = {
46 | "database": "orm.sqlite3",
47 | "prefix": "",
48 | "full_details": {"driver": "sqlite"},
49 | }
50 | command, _ = self.command.get_command(config)
51 | assert command == "sqlite3 orm.sqlite3"
52 |
53 | def test_for_mssql(self):
54 | config = {
55 | "host": "db.masonite.com",
56 | "database": "orm",
57 | "user": "root",
58 | "port": "1234",
59 | "password": "secretpostgres",
60 | "prefix": "",
61 | "options": {"charset": "utf8mb4"},
62 | "full_details": {"driver": "mssql"},
63 | }
64 | command, _ = self.command.get_command(config)
65 | assert (
66 | command
67 | == "sqlcmd -d orm -U root -P secretpostgres -S tcp:db.masonite.com,1234"
68 | )
69 |
70 | def test_running_command_with_sqlite(self):
71 | self.command_tester.execute("-c dev")
72 | assert "sqlite3" not in self.command_tester.io.fetch_output()
73 | self.command_tester.execute("-c dev -s")
74 | assert "sqlite3 orm.sqlite3" in self.command_tester.io.fetch_output()
75 |
76 | def test_hiding_sensitive_options(self):
77 | config = {
78 | "host": "localhost",
79 | "database": "orm",
80 | "user": "root",
81 | "port": "",
82 | "password": "secret",
83 | "full_details": {"driver": "mysql"},
84 | }
85 | command, _ = self.command.get_command(config)
86 | cleaned_command = self.command.hide_sensitive_options(config, command)
87 | assert (
88 | cleaned_command == "mysql orm --host localhost --user root --password ***"
89 | )
90 |
--------------------------------------------------------------------------------
/tests/connections/test_base_connections.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.connections import ConnectionResolver
4 | from tests.integrations.config.database import DB
5 |
6 |
7 | class TestDefaultBehaviorConnections(unittest.TestCase):
8 | def test_should_return_connection_with_enabled_logs(self):
9 | connection = DB.begin_transaction("dev")
10 | should_log_queries = connection.full_details.get("log_queries")
11 | DB.commit("dev")
12 |
13 | self.assertTrue(should_log_queries)
14 |
15 | def test_should_disable_log_queries_in_connection(self):
16 | connection = DB.begin_transaction("dev")
17 | connection.disable_query_log()
18 |
19 | should_log_queries = connection.full_details.get("log_queries")
20 |
21 | self.assertFalse(should_log_queries)
22 |
23 | connection.enable_query_log()
24 | should_log_queries = connection.full_details.get("log_queries")
25 |
26 | DB.commit("dev")
27 |
28 | self.assertTrue(should_log_queries)
29 |
--------------------------------------------------------------------------------
/tests/eagers/test_eager.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from src.masoniteorm.query.EagerRelation import EagerRelations
5 |
6 |
7 | class TestEagerRelation(unittest.TestCase):
8 | def test_can_register_string_eager_load(self):
9 | self.assertEqual(
10 | EagerRelations().register("profile").get_eagers(), [["profile"]]
11 | )
12 | self.assertEqual(EagerRelations().register("profile").is_nested, False)
13 | self.assertEqual(
14 | EagerRelations().register("profile.user").get_eagers(),
15 | [{"profile": ["user"]}],
16 | )
17 | self.assertEqual(
18 | EagerRelations().register("profile.user", "profile.logo").get_eagers(),
19 | [{"profile": ["user", "logo"]}],
20 | )
21 | self.assertEqual(
22 | EagerRelations()
23 | .register("profile.user", "profile.logo", "profile.bio")
24 | .get_eagers(),
25 | [{"profile": ["user", "logo", "bio"]}],
26 | )
27 | self.assertEqual(
28 | EagerRelations().register("user", "logo", "bio").get_eagers(),
29 | [["user", "logo", "bio"]],
30 | )
31 |
32 | def test_can_register_tuple_eager_load(self):
33 | self.assertEqual(
34 | EagerRelations().register(("profile",)).get_eagers(), [["profile"]]
35 | )
36 | self.assertEqual(
37 | EagerRelations().register(("profile", "user")).get_eagers(),
38 | [["profile", "user"]],
39 | )
40 | self.assertEqual(
41 | EagerRelations().register(("profile.name", "profile.user")).get_eagers(),
42 | [{"profile": ["name", "user"]}],
43 | )
44 |
45 | def test_can_register_list_eager_load(self):
46 | self.assertEqual(
47 | EagerRelations().register(["profile"]).get_eagers(), [["profile"]]
48 | )
49 | self.assertEqual(
50 | EagerRelations().register(["profile", "user"]).get_eagers(),
51 | [["profile", "user"]],
52 | )
53 | self.assertEqual(
54 | EagerRelations().register(["profile.name", "profile.user"]).get_eagers(),
55 | [{"profile": ["name", "user"]}],
56 | )
57 | self.assertEqual(
58 | EagerRelations().register(["profile.name"]).get_eagers(),
59 | [{"profile": ["name"]}],
60 | )
61 | self.assertEqual(
62 | EagerRelations().register(["profile.name", "logo"]).get_eagers(),
63 | [["logo"], {"profile": ["name"]}],
64 | )
65 | self.assertEqual(
66 | EagerRelations()
67 | .register(["profile.name", "logo", "profile.user"])
68 | .get_eagers(),
69 | [["logo"], {"profile": ["name", "user"]}],
70 | )
71 |
--------------------------------------------------------------------------------
/tests/factories/test_factories.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from src.masoniteorm import Factory as factory
5 | from src.masoniteorm.models import Model
6 | from src.masoniteorm.query import QueryBuilder
7 |
8 |
9 | class User(Model):
10 | pass
11 |
12 |
13 | class AfterCreatedModel(Model):
14 | __dry__ = True
15 | pass
16 |
17 |
18 | class TestFactories(unittest.TestCase):
19 | def setUp(self):
20 | factory.register(User, self.user_factory)
21 | factory.register(User, self.named_user_factory, name="admin")
22 | factory.register(AfterCreatedModel, self.named_user_factory)
23 | factory.after_creating(AfterCreatedModel, self.after_creating)
24 |
25 | def user_factory(self, faker):
26 | return {"id": 1, "name": faker.name()}
27 |
28 | def named_user_factory(self, faker):
29 | return {"id": 1, "name": faker.name(), "admin": 1}
30 |
31 | def after_creating(self, model, faker):
32 | model.after_created = True
33 |
34 | def test_can_make_single(self):
35 | user = factory(User).make({"id": 1, "name": "Joe"})
36 |
37 | self.assertEqual(user.name, "Joe")
38 | self.assertIsInstance(user, User)
39 |
40 | def test_can_make_several(self):
41 | users = factory(User).make([{"id": 1, "name": "Joe"}, {"id": 2, "name": "Bob"}])
42 |
43 | self.assertEqual(users.count(), 2)
44 |
45 | def test_can_make_any_number(self):
46 | users = factory(User, 50).make()
47 |
48 | self.assertEqual(users.count(), 50)
49 |
50 | def test_can_make_named_factory(self):
51 | user = factory(User).make(name="admin")
52 | self.assertEqual(user.admin, 1)
53 |
54 | def test_after_creates(self):
55 | user = factory(AfterCreatedModel).create()
56 | self.assertTrue(user.name)
57 | self.assertEqual(user.after_created, True)
58 |
59 | users = factory(AfterCreatedModel).create({"name": "billy"})
60 | self.assertEqual(users.name, "billy")
61 | self.assertEqual(users.after_created, True)
62 |
63 | users = factory(AfterCreatedModel).make()
64 | self.assertEqual(users.after_created, True)
65 |
66 | users = factory(AfterCreatedModel, 2).make()
67 | for user in users:
68 | self.assertEqual(user.after_created, True)
69 |
--------------------------------------------------------------------------------
/tests/integrations/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MasoniteFramework/orm/0d31e53f18813c2a2c910c5cee70e60d13247912/tests/integrations/config/__init__.py
--------------------------------------------------------------------------------
/tests/mssql/grammar/test_mssql_delete_grammar.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.query import QueryBuilder
4 | from src.masoniteorm.query.grammars import MSSQLGrammar
5 |
6 |
7 | class TestMySQLDeleteGrammar(unittest.TestCase):
8 | def setUp(self):
9 | self.builder = QueryBuilder(MSSQLGrammar, table="users")
10 |
11 | def test_can_compile_delete(self):
12 | to_sql = self.builder.delete("id", 1, query=True).to_sql()
13 |
14 | sql = "DELETE FROM [users] WHERE [users].[id] = '1'"
15 | self.assertEqual(to_sql, sql)
16 |
17 | def test_can_compile_delete_with_where(self):
18 | to_sql = (
19 | self.builder.where("age", 20)
20 | .where("profile", 1)
21 | .set_action("delete")
22 | .delete(query=True)
23 | .to_sql()
24 | )
25 |
26 | sql = (
27 | "DELETE FROM [users] WHERE [users].[age] = '20' AND [users].[profile] = '1'"
28 | )
29 | self.assertEqual(to_sql, sql)
30 |
--------------------------------------------------------------------------------
/tests/mssql/grammar/test_mssql_insert_grammar.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.query import QueryBuilder
4 | from src.masoniteorm.query.grammars import MSSQLGrammar
5 |
6 |
7 | class TestMySQLInsertGrammar(unittest.TestCase):
8 | def setUp(self):
9 | self.builder = QueryBuilder(MSSQLGrammar, table="users")
10 |
11 | def test_can_compile_insert(self):
12 | to_sql = self.builder.create({"name": "Joe"}, query=True).to_sql()
13 |
14 | sql = "INSERT INTO [users] ([users].[name]) VALUES ('Joe')"
15 | self.assertEqual(to_sql, sql)
16 |
17 | def test_can_compile_bulk_create(self):
18 | to_sql = self.builder.bulk_create(
19 | # These keys are intentionally out of order to show column to value alignment works
20 | [
21 | {"name": "Joe", "age": 5},
22 | {"age": 35, "name": "Bill"},
23 | {"name": "John", "age": 10},
24 | ],
25 | query=True,
26 | ).to_sql()
27 |
28 | sql = "INSERT INTO [users] ([age], [name]) VALUES ('5', 'Joe'), ('35', 'Bill'), ('10', 'John')"
29 | self.assertEqual(to_sql, sql)
30 |
31 | def test_can_compile_bulk_create_qmark(self):
32 | to_sql = self.builder.bulk_create(
33 | [{"name": "Joe"}, {"name": "Bill"}, {"name": "John"}], query=True
34 | ).to_qmark()
35 |
36 | sql = "INSERT INTO [users] ([name]) VALUES ('?'), ('?'), ('?')"
37 | self.assertEqual(to_sql, sql)
38 |
--------------------------------------------------------------------------------
/tests/mssql/grammar/test_mssql_qmark.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.query import QueryBuilder
4 | from src.masoniteorm.query.grammars import MSSQLGrammar
5 |
6 |
7 | class TestMSSQLQmark(unittest.TestCase):
8 | def setUp(self):
9 | self.builder = QueryBuilder(MSSQLGrammar, table="users")
10 |
11 | def test_can_compile_select(self):
12 | mark = self.builder.select("username").where("name", "Joe")
13 |
14 | sql = "SELECT [users].[username] FROM [users] WHERE [users].[name] = '?'"
15 | self.assertEqual(mark.to_qmark(), sql)
16 | self.assertEqual(mark._bindings, ["Joe"])
17 |
18 | def test_can_compile_update(self):
19 | mark = self.builder.update({"name": "Bob"}, dry=True).where("name", "Joe")
20 |
21 | sql = "UPDATE [users] SET [users].[name] = '?' WHERE [users].[name] = '?'"
22 | self.assertEqual(mark.to_qmark(), sql)
23 | self.assertEqual(mark._bindings, ["Bob", "Joe"])
24 |
25 | def test_can_compile_insert(self):
26 | mark = self.builder.create({"name": "Bob"}, query=True)
27 |
28 | sql = "INSERT INTO [users] ([users].[name]) VALUES ('?')"
29 | self.assertEqual(mark.to_qmark(), sql)
30 | self.assertEqual(mark._bindings, ["Bob"])
31 |
32 | def test_can_compile_where_in(self):
33 | mark = self.builder.where_in("id", [1, 2, 3])
34 |
35 | qmark_sql = "SELECT * FROM [users] WHERE [users].[id] IN ('?', '?', '?')"
36 | sql = "SELECT * FROM [users] WHERE [users].[id] IN ('1','2','3')"
37 | self.assertEqual(mark.to_qmark(), qmark_sql)
38 | self.assertEqual(mark._bindings, [1, 2, 3])
39 | self.assertEqual(self.builder.where_in("id", [1, 2, 3]).to_sql(), sql)
40 | self.builder.reset()
41 | # Assert that when passed string values it generates synonymous sql
42 | self.assertEqual(self.builder.where_in("id", ["1", "2", "3"]).to_sql(), sql)
43 |
--------------------------------------------------------------------------------
/tests/mssql/grammar/test_mssql_update_grammar.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.query import QueryBuilder
4 | from src.masoniteorm.query.grammars import MSSQLGrammar
5 | from src.masoniteorm.expressions import Raw
6 |
7 |
8 | class TestMSSQLUpdateGrammar(unittest.TestCase):
9 | def setUp(self):
10 | self.builder = QueryBuilder(MSSQLGrammar, table="users")
11 |
12 | def test_can_compile_update(self):
13 | to_sql = (
14 | self.builder.where("name", "bob").update({"name": "Joe"}, dry=True).to_sql()
15 | )
16 |
17 | sql = "UPDATE [users] SET [users].[name] = 'Joe' WHERE [users].[name] = 'bob'"
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_update_with_multiple_where(self):
21 | to_sql = (
22 | self.builder.where("name", "bob")
23 | .where("age", 20)
24 | .update({"name": "Joe"}, dry=True)
25 | .to_sql()
26 | )
27 |
28 | sql = "UPDATE [users] SET [users].[name] = 'Joe' WHERE [users].[name] = 'bob' AND [users].[age] = '20'"
29 | self.assertEqual(to_sql, sql)
30 |
31 | def test_raw_expression(self):
32 | to_sql = self.builder.update({"name": Raw("[username]")}, dry=True).to_sql()
33 |
34 | sql = "UPDATE [users] SET [users].[name] = [username]"
35 | self.assertEqual(to_sql, sql)
36 |
--------------------------------------------------------------------------------
/tests/mysql/builder/test_mysql_builder_transaction.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import os
3 | import unittest
4 |
5 | from src.masoniteorm.connections import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import MySQLGrammar
9 | from src.masoniteorm.relationships import belongs_to
10 | from tests.utils import MockConnectionFactory
11 | from tests.integrations.config.database import DB
12 |
13 | if os.getenv("RUN_MYSQL_DATABASE") == "True":
14 |
15 | class User(Model):
16 | __connection__ = "mysql"
17 | __timestamps__ = False
18 |
19 | class BaseTestQueryRelationships(unittest.TestCase):
20 | maxDiff = None
21 |
22 | def get_builder(self, table="users"):
23 | connection = ConnectionFactory().make("mysql")
24 | return QueryBuilder(
25 | grammar=MySQLGrammar, connection=connection, table=table
26 | ).on("mysql")
27 |
28 | def test_transaction(self):
29 | builder = self.get_builder()
30 | builder.begin()
31 | builder.create({"name": "phillip2", "email": "phillip2"})
32 | # builder.commit()
33 | user = builder.where("name", "phillip2").first()
34 | self.assertEqual(user["name"], "phillip2")
35 | builder.rollback()
36 | user = builder.where("name", "phillip2").first()
37 | self.assertEqual(user, None)
38 |
39 | def test_transaction_default_globally(self):
40 | connection = DB.begin_transaction()
41 | self.assertEqual(connection, self.get_builder().new_connection())
42 | DB.commit()
43 | DB.begin_transaction()
44 | DB.rollback()
45 |
--------------------------------------------------------------------------------
/tests/mysql/builder/test_query_builder_scopes.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from tests.integrations.config.database import DATABASES
5 | from src.masoniteorm.models import Model
6 | from src.masoniteorm.query import QueryBuilder
7 | from src.masoniteorm.query.grammars import MySQLGrammar
8 | from src.masoniteorm.relationships import has_many
9 | from src.masoniteorm.scopes import SoftDeleteScope
10 | from tests.utils import MockConnectionFactory
11 |
12 |
13 | class BaseTestQueryBuilderScopes(unittest.TestCase):
14 | grammar = "mysql"
15 |
16 | def get_builder(self, table="users"):
17 | connection = MockConnectionFactory().make("default")
18 | return QueryBuilder(
19 | grammar=MySQLGrammar,
20 | connection_class=connection,
21 | connection="mysql",
22 | table=table,
23 | connection_details=DATABASES,
24 | )
25 |
26 | def test_scopes(self):
27 | builder = self.get_builder().set_scope(
28 | "gender", lambda model, q: q.where("gender", "w")
29 | )
30 |
31 | self.assertEqual(
32 | builder.gender().where("id", 1).to_sql(),
33 | "SELECT * FROM `users` WHERE `users`.`gender` = 'w' AND `users`.`id` = '1'",
34 | )
35 |
36 | def test_global_scopes(self):
37 | builder = self.get_builder().set_global_scope(
38 | "where_not_null", lambda q: q.where_not_null("deleted_at"), action="select"
39 | )
40 |
41 | self.assertEqual(
42 | builder.where("id", 1).to_sql(),
43 | "SELECT * FROM `users` WHERE `users`.`id` = '1' AND `users`.`deleted_at` IS NOT NULL",
44 | )
45 |
46 | def test_global_scope_from_class(self):
47 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
48 |
49 | self.assertEqual(
50 | builder.where("id", 1).to_sql(),
51 | "SELECT * FROM `users` WHERE `users`.`id` = '1' AND `users`.`deleted_at` IS NULL",
52 | )
53 |
54 | def test_global_scope_remove_from_class(self):
55 | builder = (
56 | self.get_builder()
57 | .set_global_scope(SoftDeleteScope())
58 | .remove_global_scope(SoftDeleteScope())
59 | )
60 |
61 | self.assertEqual(
62 | builder.where("id", 1).to_sql(),
63 | "SELECT * FROM `users` WHERE `users`.`id` = '1'",
64 | )
65 |
66 | def test_global_scope_adds_method(self):
67 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
68 |
69 | self.assertEqual(builder.with_trashed().to_sql(), "SELECT * FROM `users`")
70 |
--------------------------------------------------------------------------------
/tests/mysql/builder/test_transactions.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import os
3 | import unittest
4 |
5 | from src.masoniteorm.connections.ConnectionFactory import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import MySQLGrammar
9 | from src.masoniteorm.relationships import has_many
10 | from tests.utils import MockConnectionFactory
11 |
12 |
13 | class Articles(Model):
14 | pass
15 |
16 |
17 | class User(Model):
18 | @has_many("id", "user_id")
19 | def articles(self):
20 | return Articles
21 |
22 |
23 | if os.getenv("RUN_MYSQL_DATABASE", False) == "True":
24 |
25 | class TestTransactions(unittest.TestCase):
26 | pass
27 | # def get_builder(self, table="users"):
28 | # connection = ConnectionFactory().make("default")
29 | # return QueryBuilder(MySQLGrammar, connection, table=table, model=User())
30 |
31 | # def test_can_start_transaction(self, table="users"):
32 | # builder = self.get_builder()
33 | # builder.begin()
34 | # builder.create({"name": "mike", "email": "mike@email.com"})
35 | # builder.rollback()
36 | # self.assertFalse(builder.where("email", "mike@email.com").first())
37 |
--------------------------------------------------------------------------------
/tests/mysql/connections/test_mysql_connection_selects.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from src.masoniteorm.models import Model
5 | from src.masoniteorm.query import QueryBuilder
6 | from src.masoniteorm.query.grammars import MySQLGrammar
7 |
8 |
9 | class MockUser(Model):
10 | __table__ = "users"
11 |
12 |
13 | if os.getenv("RUN_MYSQL_DATABASE", False) == "True":
14 |
15 | class TestMySQLSelectConnection(unittest.TestCase):
16 | def setUp(self):
17 | self.builder = QueryBuilder(MySQLGrammar, table="users")
18 |
19 | def test_can_compile_select(self):
20 | to_sql = MockUser.where("id", 1).to_sql()
21 |
22 | sql = "SELECT * FROM `users` WHERE `users`.`id` = '1'"
23 | self.assertEqual(to_sql, sql)
24 |
25 | def test_can_get_first_record(self):
26 | user = MockUser.where("id", 1).first()
27 | self.assertEqual(user.id, 1)
28 |
29 | def test_can_find_first_record(self):
30 | user = MockUser.find(1)
31 | self.assertEqual(user.id, 1)
32 |
33 | def test_can_get_all_records(self):
34 | users = MockUser.all()
35 | self.assertGreater(len(users), 1)
36 |
37 | def test_can_get_5_records(self):
38 | users = MockUser.limit(5).get()
39 | self.assertEqual(len(users), 5)
40 |
41 | def test_can_get_1_record_with_get(self):
42 | users = MockUser.where("id", 1).limit(5).get()
43 | self.assertEqual(len(users), 1)
44 |
45 | users = MockUser.limit(5).where("id", 1).get()
46 | self.assertEqual(len(users), 1)
47 |
--------------------------------------------------------------------------------
/tests/mysql/grammar/test_mysql_delete_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import MySQLGrammar
6 |
7 |
8 | class BaseDeleteGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(MySQLGrammar, table="users")
11 |
12 | def test_can_compile_delete(self):
13 | to_sql = self.builder.delete("id", 1, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_delete_in(self):
21 | to_sql = self.builder.delete("id", [1, 2, 3], query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_delete_with_where(self):
29 | to_sql = (
30 | self.builder.where("age", 20)
31 | .where("profile", 1)
32 | .set_action("delete")
33 | .delete(query=True)
34 | .to_sql()
35 | )
36 |
37 | sql = getattr(
38 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
39 | )()
40 | self.assertEqual(to_sql, sql)
41 |
42 |
43 | class TestMySQLDeleteGrammar(BaseDeleteGrammarTest, unittest.TestCase):
44 | grammar = "mysql"
45 |
46 | def can_compile_delete(self):
47 | """
48 | (
49 | self.builder
50 | .delete('id', 1)
51 | .to_sql()
52 | )
53 | """
54 | return "DELETE FROM `users` WHERE `users`.`id` = '1'"
55 |
56 | def can_compile_delete_in(self):
57 | """
58 | (
59 | self.builder
60 | .delete('id', 1)
61 | .to_sql()
62 | )
63 | """
64 | return "DELETE FROM `users` WHERE `users`.`id` IN ('1','2','3')"
65 |
66 | def can_compile_delete_with_where(self):
67 | """
68 | (
69 | self.builder
70 | .where('age', 20)
71 | .where('profile', 1)
72 | .set_action('delete')
73 | .delete()
74 | .to_sql()
75 | )
76 | """
77 | return (
78 | "DELETE FROM `users` WHERE `users`.`age` = '20' AND `users`.`profile` = '1'"
79 | )
80 |
--------------------------------------------------------------------------------
/tests/mysql/grammar/test_mysql_insert_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import MySQLGrammar
6 |
7 |
8 | class BaseInsertGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(MySQLGrammar, table="users")
11 |
12 | def test_can_compile_insert(self):
13 | to_sql = self.builder.create({"name": "Joe"}, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_insert_with_keywords(self):
21 | to_sql = self.builder.create(name="Joe", query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_bulk_create(self):
29 | to_sql = self.builder.bulk_create(
30 | # These keys are intentionally out of order to show column to value alignment works
31 | [
32 | {"name": "Joe", "age": 5},
33 | {"age": 35, "name": "Bill"},
34 | {"name": "John", "age": 10},
35 | ],
36 | query=True,
37 | ).to_sql()
38 |
39 | sql = getattr(
40 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
41 | )()
42 | self.assertEqual(to_sql, sql)
43 |
44 | def test_can_compile_bulk_create_qmark(self):
45 | to_sql = self.builder.bulk_create(
46 | [{"name": "Joe"}, {"name": "Bill"}, {"name": "John"}], query=True
47 | ).to_qmark()
48 |
49 | sql = getattr(
50 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
51 | )()
52 | self.assertEqual(to_sql, sql)
53 |
54 | def test_can_compile_bulk_create_multiple(self):
55 | to_sql = self.builder.bulk_create(
56 | [
57 | {"name": "Joe", "active": "1"},
58 | {"name": "Bill", "active": "1"},
59 | {"name": "John", "active": "1"},
60 | ],
61 | query=True,
62 | ).to_sql()
63 |
64 | sql = getattr(
65 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
66 | )()
67 | self.assertEqual(to_sql, sql)
68 |
69 |
70 | class TestMySQLUpdateGrammar(BaseInsertGrammarTest, unittest.TestCase):
71 | grammar = "mysql"
72 |
73 | def can_compile_insert(self):
74 | """
75 | self.builder.create({
76 | 'name': 'Joe'
77 | }).to_sql()
78 | """
79 | return "INSERT INTO `users` (`users`.`name`) VALUES ('Joe')"
80 |
81 | def can_compile_insert_with_keywords(self):
82 | """
83 | self.builder.create(name="Joe").to_sql()
84 | """
85 | return "INSERT INTO `users` (`users`.`name`) VALUES ('Joe')"
86 |
87 | def can_compile_bulk_create(self):
88 | """
89 | self.builder.create(name="Joe").to_sql()
90 | """
91 | return """INSERT INTO `users` (`age`, `name`) VALUES ('5', 'Joe'), ('35', 'Bill'), ('10', 'John')"""
92 |
93 | def can_compile_bulk_create_multiple(self):
94 | """
95 | self.builder.create(name="Joe").to_sql()
96 | """
97 | return """INSERT INTO `users` (`active`, `name`) VALUES ('1', 'Joe'), ('1', 'Bill'), ('1', 'John')"""
98 |
99 | def can_compile_bulk_create_qmark(self):
100 | """
101 | self.builder.create(name="Joe").to_sql()
102 | """
103 | return """INSERT INTO `users` (`name`) VALUES ('?'), ('?'), ('?')"""
104 |
--------------------------------------------------------------------------------
/tests/mysql/grammar/test_mysql_update_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import MySQLGrammar
6 | from src.masoniteorm.expressions import Raw
7 |
8 |
9 | class BaseTestCaseUpdateGrammar:
10 | def setUp(self):
11 | self.builder = QueryBuilder(self.grammar, table="users")
12 |
13 | def test_can_compile_update(self):
14 | to_sql = (
15 | self.builder.where("name", "bob").update({"name": "Joe"}, dry=True).to_sql()
16 | )
17 |
18 | sql = getattr(
19 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
20 | )()
21 | self.assertEqual(to_sql, sql)
22 |
23 | def test_can_compile_multiple_update(self):
24 | to_sql = self.builder.update(
25 | {"name": "Joe", "email": "user@email.com"}, dry=True
26 | ).to_sql()
27 |
28 | sql = getattr(
29 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
30 | )()
31 | self.assertEqual(to_sql, sql)
32 |
33 | def test_can_compile_update_with_multiple_where(self):
34 | to_sql = (
35 | self.builder.where("name", "bob")
36 | .where("age", 20)
37 | .update({"name": "Joe"}, dry=True)
38 | .to_sql()
39 | )
40 |
41 | sql = getattr(
42 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
43 | )()
44 | self.assertEqual(to_sql, sql)
45 |
46 | # def test_can_compile_increment(self):
47 | # to_sql = self.builder.increment("age")
48 |
49 | # sql = getattr(
50 | # self, inspect.currentframe().f_code.co_name.replace("test_", "")
51 | # )()
52 | # self.assertEqual(to_sql, sql)
53 |
54 | # def test_can_compile_decrement(self):
55 | # to_sql = self.builder.decrement("age", 20).to_sql()
56 |
57 | # sql = getattr(
58 | # self, inspect.currentframe().f_code.co_name.replace("test_", "")
59 | # )()
60 | # self.assertEqual(to_sql, sql)
61 |
62 | def test_raw_expression(self):
63 | to_sql = self.builder.update({"name": Raw("`username`")}, dry=True).to_sql()
64 |
65 | sql = getattr(
66 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
67 | )()
68 |
69 | self.assertEqual(to_sql, sql)
70 |
71 |
72 | class TestMySQLUpdateGrammar(BaseTestCaseUpdateGrammar, unittest.TestCase):
73 | grammar = MySQLGrammar
74 |
75 | def can_compile_update(self):
76 | """
77 | builder.where('name', 'bob').update({
78 | 'name': 'Joe'
79 | }).to_sql()
80 | """
81 | return "UPDATE `users` SET `users`.`name` = 'Joe' WHERE `users`.`name` = 'bob'"
82 |
83 | def raw_expression(self):
84 | """
85 | builder.where('name', 'bob').update({
86 | 'name': 'Joe'
87 | }).to_sql()
88 | """
89 | return "UPDATE `users` SET `users`.`name` = `username`"
90 |
91 | def can_compile_multiple_update(self):
92 | """
93 | self.builder.update({"name": "Joe", "email": "user@email.com"}, dry=True).to_sql()
94 | """
95 | return "UPDATE `users` SET `users`.`name` = 'Joe', `users`.`email` = 'user@email.com'"
96 |
97 | def can_compile_update_with_multiple_where(self):
98 | """
99 | builder.where('name', 'bob').where('age', 20).update({
100 | 'name': 'Joe'
101 | }).to_sql()
102 | """
103 | return "UPDATE `users` SET `users`.`name` = 'Joe' WHERE `users`.`name` = 'bob' AND `users`.`age` = '20'"
104 |
105 | def can_compile_increment(self):
106 | """
107 | builder.increment('age').to_sql()
108 | """
109 | return "UPDATE `users` SET `users`.`age` = `users`.`age` + '1'"
110 |
111 | def can_compile_decrement(self):
112 | """
113 | builder.decrement('age', 20).to_sql()
114 | """
115 | return "UPDATE `users` SET `users`.`age` = `users`.`age` - '20'"
116 |
--------------------------------------------------------------------------------
/tests/mysql/model/test_accessors_and_mutators.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | import os
4 | import unittest
5 |
6 | import pendulum
7 |
8 | from src.masoniteorm.collection import Collection
9 | from src.masoniteorm.models import Model
10 | from src.masoniteorm.query.grammars import MSSQLGrammar
11 | from tests.User import User
12 |
13 |
14 | class User(Model):
15 | __casts__ = {"is_admin": "bool"}
16 |
17 | def get_name_attribute(self):
18 | return f"Hello, {self.get_raw_attribute('name')}"
19 |
20 | def set_name_attribute(self, attribute):
21 | return str(attribute).upper()
22 |
23 |
24 | class SetUser(Model):
25 | __casts__ = {"is_admin": "bool"}
26 |
27 | def set_name_attribute(self, attribute):
28 | return str(attribute).upper()
29 |
30 |
31 | class TestAccessor(unittest.TestCase):
32 | def test_can_get_accessor(self):
33 | user = User.hydrate(
34 | {"name": "joe", "email": "joe@masoniteproject.com", "is_admin": 1}
35 | )
36 | self.assertEqual(user.email, "joe@masoniteproject.com")
37 | self.assertEqual(user.name, "Hello, joe")
38 | self.assertTrue(user.is_admin is True, f"{user.is_admin} is not True")
39 |
40 | def test_mutator(self):
41 | user = SetUser.hydrate({"email": "joe@masoniteproject.com", "is_admin": 1})
42 |
43 | user.name = "joe"
44 |
45 | self.assertEqual(user.name, "JOE")
46 |
--------------------------------------------------------------------------------
/tests/mysql/relationships/test_has_many_through.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.models import Model
4 | from src.masoniteorm.relationships import (
5 | has_many_through,
6 | )
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv(".env")
10 |
11 |
12 | class InboundShipment(Model):
13 | @has_many_through("port_id", "country_id", "from_port_id", "country_id")
14 | def from_country(self):
15 | return Country, Port
16 |
17 |
18 | class Country(Model):
19 | pass
20 |
21 |
22 | class Port(Model):
23 | pass
24 |
25 |
26 | class MySQLRelationships(unittest.TestCase):
27 | maxDiff = None
28 |
29 | def test_has_query(self):
30 | sql = InboundShipment.has("from_country").to_sql()
31 |
32 | self.assertEqual(
33 | sql,
34 | """SELECT * FROM `inbound_shipments` WHERE EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
35 | )
36 |
37 | def test_or_has(self):
38 | sql = InboundShipment.where("name", "Joe").or_has("from_country").to_sql()
39 |
40 | self.assertEqual(
41 | sql,
42 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
43 | )
44 |
45 | def test_where_has_query(self):
46 | sql = InboundShipment.where_has(
47 | "from_country", lambda query: query.where("name", "USA")
48 | ).to_sql()
49 |
50 | self.assertEqual(
51 | sql,
52 | """SELECT * FROM `inbound_shipments` WHERE EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
53 | )
54 |
55 | def test_or_where_has(self):
56 | sql = (
57 | InboundShipment.where("name", "Joe")
58 | .or_where_has("from_country", lambda query: query.where("name", "USA"))
59 | .to_sql()
60 | )
61 |
62 | self.assertEqual(
63 | sql,
64 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
65 | )
66 |
67 | def test_doesnt_have(self):
68 | sql = InboundShipment.doesnt_have("from_country").to_sql()
69 |
70 | self.assertEqual(
71 | sql,
72 | """SELECT * FROM `inbound_shipments` WHERE NOT EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
73 | )
74 |
75 | def test_or_where_doesnt_have(self):
76 | sql = (
77 | InboundShipment.where("name", "Joe")
78 | .or_where_doesnt_have(
79 | "from_country", lambda query: query.where("name", "USA")
80 | )
81 | .to_sql()
82 | )
83 |
84 | self.assertEqual(
85 | sql,
86 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR NOT EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
87 | )
88 |
--------------------------------------------------------------------------------
/tests/mysql/relationships/test_has_one_through.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.masoniteorm.models import Model
4 | from src.masoniteorm.relationships import (
5 | has_one_through,
6 | )
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv(".env")
10 |
11 |
12 | class InboundShipment(Model):
13 | @has_one_through(None, "from_port_id", "country_id", "port_id", "country_id")
14 | def from_country(self):
15 | return Country, Port
16 |
17 |
18 | class Country(Model):
19 | pass
20 |
21 |
22 | class Port(Model):
23 | pass
24 |
25 |
26 | class MySQLHasOneThroughRelationship(unittest.TestCase):
27 | maxDiff = None
28 |
29 | def test_has_query(self):
30 | sql = InboundShipment.has("from_country").to_sql()
31 |
32 | self.assertEqual(
33 | sql,
34 | """SELECT * FROM `inbound_shipments` WHERE EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
35 | )
36 |
37 | def test_or_has(self):
38 | sql = InboundShipment.where("name", "Joe").or_has("from_country").to_sql()
39 |
40 | self.assertEqual(
41 | sql,
42 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
43 | )
44 |
45 | def test_where_has_query(self):
46 | sql = InboundShipment.where_has(
47 | "from_country", lambda query: query.where("name", "USA")
48 | ).to_sql()
49 |
50 | self.assertEqual(
51 | sql,
52 | """SELECT * FROM `inbound_shipments` WHERE EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
53 | )
54 |
55 | def test_or_where_has(self):
56 | sql = (
57 | InboundShipment.where("name", "Joe")
58 | .or_where_has("from_country", lambda query: query.where("name", "USA"))
59 | .to_sql()
60 | )
61 |
62 | self.assertEqual(
63 | sql,
64 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
65 | )
66 |
67 | def test_doesnt_have(self):
68 | sql = InboundShipment.doesnt_have("from_country").to_sql()
69 |
70 | self.assertEqual(
71 | sql,
72 | """SELECT * FROM `inbound_shipments` WHERE NOT EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`)""",
73 | )
74 |
75 | def test_or_where_doesnt_have(self):
76 | sql = (
77 | InboundShipment.where("name", "Joe")
78 | .or_where_doesnt_have(
79 | "from_country", lambda query: query.where("name", "USA")
80 | )
81 | .to_sql()
82 | )
83 |
84 | self.assertEqual(
85 | sql,
86 | """SELECT * FROM `inbound_shipments` WHERE `inbound_shipments`.`name` = 'Joe' OR NOT EXISTS (SELECT * FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id` AND `countries`.`name` = 'USA')""",
87 | )
88 |
89 | def test_has_one_through_with_count(self):
90 | sql = InboundShipment.with_count("from_country").to_sql()
91 |
92 | self.assertEqual(
93 | sql,
94 | """SELECT `inbound_shipments`.*, (SELECT COUNT(*) AS m_count_reserved FROM `countries` INNER JOIN `ports` ON `ports`.`country_id` = `countries`.`country_id` WHERE `ports`.`port_id` = `inbound_shipments`.`from_port_id`) AS from_country_count FROM `inbound_shipments`""",
95 | )
96 |
--------------------------------------------------------------------------------
/tests/mysql/scopes/test_can_use_global_scopes.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.models import Model
5 | from src.masoniteorm.scopes import (
6 | SoftDeleteScope,
7 | SoftDeletesMixin,
8 | TimeStampsMixin,
9 | scope,
10 | )
11 |
12 |
13 | class UserSoft(Model, SoftDeletesMixin):
14 | __dry__ = True
15 |
16 |
17 | class User(Model):
18 | __dry__ = True
19 |
20 |
21 | class TestMySQLGlobalScopes(unittest.TestCase):
22 | def test_can_use_global_scopes_on_select(self):
23 | sql = "SELECT * FROM `user_softs` WHERE `user_softs`.`name` = 'joe' AND `user_softs`.`deleted_at` IS NULL"
24 | self.assertEqual(sql, UserSoft.where("name", "joe").to_sql())
25 |
26 | # def test_can_use_global_scopes_on_delete(self):
27 | # sql = "UPDATE `users` SET `users`.`deleted_at` = 'now' WHERE `users`.`name` = 'joe'"
28 | # self.assertEqual(
29 | # sql,
30 | # User.apply_scope(SoftDeletes)
31 | # .where("name", "joe")
32 | # .delete(query=True)
33 | # .to_sql(),
34 | # )
35 |
36 | def test_can_use_global_scopes_on_time(self):
37 | sql = "INSERT INTO `users` (`users`.`name`, `users`.`updated_at`, `users`.`created_at`) VALUES ('Joe'"
38 | self.assertTrue(User.create({"name": "Joe"}, query=True).to_sql().startswith(sql))
39 |
40 | # def test_can_use_global_scopes_on_inherit(self):
41 | # sql = "SELECT * FROM `user_softs` WHERE `user_softs`.`deleted_at` IS NULL"
42 | # self.assertEqual(sql, UserSoft.all(query=True))
43 |
--------------------------------------------------------------------------------
/tests/mysql/scopes/test_can_use_scopes.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.models import Model
5 | from src.masoniteorm.scopes import SoftDeletesMixin, scope
6 | from tests.User import User
7 |
8 |
9 | class User(Model):
10 | __dry__ = True
11 |
12 | @scope
13 | def active(self, query, status):
14 | return query.where("active", status)
15 |
16 | @scope
17 | def gender(self, query, status):
18 | return query.where("gender", status)
19 |
20 |
21 | class UserSoft(Model, SoftDeletesMixin):
22 | __dry__ = True
23 |
24 |
25 | class TestMySQLScopes(unittest.TestCase):
26 | def test_can_get_sql(self):
27 | sql = "SELECT * FROM `users` WHERE `users`.`name` = 'joe'"
28 | self.assertEqual(sql, User.where("name", "joe").to_sql())
29 |
30 | def test_active_scope(self):
31 | sql = "SELECT * FROM `users` WHERE `users`.`name` = 'joe' AND `users`.`active` = '1'"
32 | self.assertEqual(sql, User.where("name", "joe").active(1).to_sql())
33 |
34 | def test_active_scope_with_params(self):
35 | sql = "SELECT * FROM `users` WHERE `users`.`active` = '2' AND `users`.`name` = 'joe'"
36 | self.assertEqual(sql, User.active(2).where("name", "joe").to_sql())
37 |
38 | def test_can_chain_scopes(self):
39 | sql = "SELECT * FROM `users` WHERE `users`.`active` = '2' AND `users`.`gender` = 'W' AND `users`.`name` = 'joe'"
40 | self.assertEqual(sql, User.active(2).gender("W").where("name", "joe").to_sql())
41 |
--------------------------------------------------------------------------------
/tests/mysql/scopes/test_soft_delete.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import pendulum
4 |
5 | from tests.integrations.config.database import DATABASES
6 | from src.masoniteorm.query import QueryBuilder
7 | from src.masoniteorm.query.grammars import MySQLGrammar
8 | from src.masoniteorm.scopes import SoftDeleteScope
9 | from tests.utils import MockConnectionFactory
10 |
11 | from src.masoniteorm.models import Model
12 | from src.masoniteorm.scopes import SoftDeletesMixin
13 |
14 |
15 | class UserSoft(Model, SoftDeletesMixin):
16 | __dry__ = True
17 | __table__ = "users"
18 |
19 | class UserSoftArchived(Model, SoftDeletesMixin):
20 | __dry__ = True
21 | __deleted_at__ = "archived_at"
22 | __table__ = "users"
23 |
24 |
25 | class TestSoftDeleteScope(unittest.TestCase):
26 | def get_builder(self, table="users"):
27 | connection = MockConnectionFactory().make("default")
28 | return QueryBuilder(
29 | grammar=MySQLGrammar,
30 | connection_class=connection,
31 | connection="mysql",
32 | table=table,
33 | connection_details=DATABASES,
34 | dry=True,
35 | )
36 |
37 | def test_with_trashed(self):
38 | sql = "SELECT * FROM `users`"
39 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
40 | self.assertEqual(sql, builder.with_trashed().to_sql())
41 |
42 | def test_force_delete(self):
43 | sql = "DELETE FROM `users`"
44 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
45 | self.assertEqual(sql, builder.force_delete(query=True).to_sql())
46 |
47 | def test_restore(self):
48 | sql = "UPDATE `users` SET `users`.`deleted_at` = 'None'"
49 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
50 | self.assertEqual(sql, builder.restore().to_sql())
51 |
52 | def test_force_delete_with_wheres(self):
53 | sql = "DELETE FROM `users` WHERE `users`.`active` = '1'"
54 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
55 | self.assertEqual(
56 | sql, UserSoft.where("active", 1).force_delete(query=True).to_sql()
57 | )
58 |
59 | def test_that_trashed_users_are_not_returned_by_default(self):
60 | sql = "SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL"
61 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
62 | self.assertEqual(sql, builder.to_sql())
63 |
64 | def test_only_trashed(self):
65 | sql = "SELECT * FROM `users` WHERE `users`.`deleted_at` IS NOT NULL"
66 | builder = self.get_builder().set_global_scope(SoftDeleteScope())
67 | self.assertEqual(sql, builder.only_trashed().to_sql())
68 |
69 | def test_only_trashed_on_model(self):
70 | sql = "SELECT * FROM `users` WHERE `users`.`deleted_at` IS NOT NULL"
71 | self.assertEqual(sql, UserSoft.only_trashed().to_sql())
72 |
73 | def test_can_change_column(self):
74 | sql = "SELECT * FROM `users` WHERE `users`.`archived_at` IS NOT NULL"
75 | self.assertEqual(sql, UserSoftArchived.only_trashed().to_sql())
76 |
77 | def test_find_with_global_scope(self):
78 | find_sql = UserSoft.find("1", query=True).to_sql()
79 | raw_sql = """SELECT * FROM `users` WHERE `users`.`id` = '1' AND `users`.`deleted_at` IS NULL"""
80 | self.assertEqual(find_sql, raw_sql)
81 |
82 | def test_find_with_trashed_scope(self):
83 | find_sql = UserSoft.with_trashed().find("1", query=True).to_sql()
84 | raw_sql = """SELECT * FROM `users` WHERE `users`.`id` = '1'"""
85 | self.assertEqual(find_sql, raw_sql)
86 |
87 | def test_find_with_only_trashed_scope(self):
88 | find_sql = UserSoft.only_trashed().find("1", query=True).to_sql()
89 | raw_sql = """SELECT * FROM `users` WHERE `users`.`deleted_at` IS NOT NULL AND `users`.`id` = '1'"""
90 | self.assertEqual(find_sql, raw_sql)
91 |
--------------------------------------------------------------------------------
/tests/postgres/builder/test_postgres_transaction.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import os
3 | import unittest
4 |
5 | from tests.integrations.config.database import DATABASES
6 | from src.masoniteorm.connections import ConnectionFactory
7 | from src.masoniteorm.models import Model
8 | from src.masoniteorm.query import QueryBuilder
9 | from src.masoniteorm.query.grammars import PostgresGrammar
10 | from src.masoniteorm.relationships import belongs_to
11 | from tests.utils import MockConnectionFactory
12 |
13 | if os.getenv("RUN_POSTGRES_DATABASE") == "True":
14 |
15 | class User(Model):
16 | __connection__ = "postgres"
17 | __timestamps__ = False
18 |
19 | class BaseTestQueryRelationships(unittest.TestCase):
20 | maxDiff = None
21 |
22 | def get_builder(self, table="users"):
23 | connection = ConnectionFactory().make("postgres")
24 | return QueryBuilder(
25 | grammar=PostgresGrammar,
26 | connection=connection,
27 | table=table,
28 | connection_details=DATABASES,
29 | ).on("postgres")
30 |
31 | def test_transaction(self):
32 | builder = self.get_builder()
33 | builder.begin()
34 | builder.create({"name": "phillip2", "email": "phillip2"})
35 | # builder.commit()
36 | user = builder.where("name", "phillip2").first()
37 | self.assertEqual(user["name"], "phillip2")
38 | builder.rollback()
39 | user = builder.where("name", "phillip2").first()
40 | self.assertEqual(user, None)
41 |
--------------------------------------------------------------------------------
/tests/postgres/grammar/test_delete_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import PostgresGrammar
6 |
7 |
8 | class BaseDeleteGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(PostgresGrammar, table="users")
11 |
12 | def test_can_compile_delete(self):
13 | to_sql = self.builder.delete("id", 1, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_delete_in(self):
21 | to_sql = self.builder.delete("id", [1, 2, 3], query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_delete_with_where(self):
29 | to_sql = (
30 | self.builder.where("age", 20)
31 | .where("profile", 1)
32 | .set_action("delete")
33 | .delete(query=True)
34 | .to_sql()
35 | )
36 |
37 | sql = getattr(
38 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
39 | )()
40 | self.assertEqual(to_sql, sql)
41 |
42 |
43 | class TestPostgresDeleteGrammar(BaseDeleteGrammarTest, unittest.TestCase):
44 | grammar = "postgres"
45 |
46 | def can_compile_delete(self):
47 | """
48 | (
49 | self.builder
50 | .delete('id', 1)
51 | .to_sql()
52 | )
53 | """
54 | return """DELETE FROM "users" WHERE "users"."id" = '1'"""
55 |
56 | def can_compile_delete_in(self):
57 | """
58 | (
59 | self.builder
60 | .delete('id', 1)
61 | .to_sql()
62 | )
63 | """
64 | return """DELETE FROM "users" WHERE "users"."id" IN ('1','2','3')"""
65 |
66 | def can_compile_delete_with_where(self):
67 | """
68 | (
69 | self.builder
70 | .where('age', 20)
71 | .where('profile', 1)
72 | .set_action('delete')
73 | .delete()
74 | .to_sql()
75 | )
76 | """
77 | return """DELETE FROM "users" WHERE "users"."age" = '20' AND "users"."profile" = '1'"""
78 |
--------------------------------------------------------------------------------
/tests/postgres/grammar/test_insert_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import PostgresGrammar
6 |
7 |
8 | class BaseInsertGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(PostgresGrammar, table="users")
11 |
12 | def test_can_compile_insert(self):
13 | to_sql = self.builder.create({"name": "Joe"}, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_insert_with_keywords(self):
21 | to_sql = self.builder.create(name="Joe", query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_bulk_create(self):
29 | to_sql = self.builder.bulk_create(
30 | # These keys are intentionally out of order to show column to value alignment works
31 | [
32 | {"name": "Joe", "age": 5},
33 | {"age": 35, "name": "Bill"},
34 | {"name": "John", "age": 10},
35 | ],
36 | query=True,
37 | ).to_sql()
38 |
39 | sql = getattr(
40 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
41 | )()
42 | self.assertEqual(to_sql, sql)
43 |
44 | def test_can_compile_bulk_create_qmark(self):
45 | to_sql = self.builder.bulk_create(
46 | [{"name": "Joe"}, {"name": "Bill"}, {"name": "John"}], query=True
47 | ).to_qmark()
48 |
49 | sql = getattr(
50 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
51 | )()
52 | self.assertEqual(to_sql, sql)
53 |
54 |
55 | class TestPostgresUpdateGrammar(BaseInsertGrammarTest, unittest.TestCase):
56 | grammar = "postgres"
57 |
58 | def can_compile_insert(self):
59 | """
60 | self.builder.create({
61 | 'name': 'Joe'
62 | }).to_sql()
63 | """
64 | return """INSERT INTO "users" ("name") VALUES ('Joe') RETURNING *"""
65 |
66 | def can_compile_insert_with_keywords(self):
67 | """
68 | self.builder.create(name="Joe").to_sql()
69 | """
70 | return """INSERT INTO "users" ("name") VALUES ('Joe') RETURNING *"""
71 |
72 | def can_compile_bulk_create(self):
73 | """
74 | self.builder.create(name="Joe").to_sql()
75 | """
76 | return """INSERT INTO "users" ("age", "name") VALUES ('5', 'Joe'), ('35', 'Bill'), ('10', 'John') RETURNING *"""
77 |
78 | def can_compile_bulk_create_qmark(self):
79 | """
80 | self.builder.create(name="Joe").to_sql()
81 | """
82 | return """INSERT INTO "users" ("name") VALUES ('?'), ('?'), ('?') RETURNING *"""
83 |
--------------------------------------------------------------------------------
/tests/postgres/grammar/test_update_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.connections import PostgresConnection
5 | from src.masoniteorm.query import QueryBuilder
6 | from src.masoniteorm.query.grammars import PostgresGrammar
7 | from src.masoniteorm.expressions import Raw
8 |
9 |
10 | class BaseTestCaseUpdateGrammar:
11 | def setUp(self):
12 | self.builder = QueryBuilder(
13 | PostgresGrammar, connection_class=PostgresConnection, table="users"
14 | )
15 |
16 | def test_can_compile_update(self):
17 | to_sql = (
18 | self.builder.where("name", "bob").update({"name": "Joe"}, dry=True).to_sql()
19 | )
20 |
21 | sql = getattr(
22 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
23 | )()
24 | self.assertEqual(to_sql, sql)
25 |
26 | def test_can_compile_multiple_update(self):
27 | to_sql = self.builder.update(
28 | {"name": "Joe", "email": "user@email.com"}, dry=True
29 | ).to_sql()
30 |
31 | sql = getattr(
32 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
33 | )()
34 | self.assertEqual(to_sql, sql)
35 |
36 | def test_can_compile_update_with_multiple_where(self):
37 | to_sql = (
38 | self.builder.where("name", "bob")
39 | .where("age", 20)
40 | .update({"name": "Joe"}, dry=True)
41 | .to_sql()
42 | )
43 |
44 | sql = getattr(
45 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
46 | )()
47 | self.assertEqual(to_sql, sql)
48 |
49 | # def test_can_compile_increment(self):
50 | # to_sql = self.builder.increment("age").to_sql()
51 |
52 | # sql = getattr(
53 | # self, inspect.currentframe().f_code.co_name.replace("test_", "")
54 | # )()
55 | # self.assertEqual(to_sql, sql)
56 |
57 | # def test_can_compile_decrement(self):
58 | # to_sql = self.builder.decrement("age", 20).to_sql()
59 |
60 | # sql = getattr(
61 | # self, inspect.currentframe().f_code.co_name.replace("test_", "")
62 | # )()
63 | # self.assertEqual(to_sql, sql)
64 |
65 | def test_raw_expression(self):
66 | to_sql = self.builder.update({"name": Raw('"username"')}, dry=True).to_sql()
67 |
68 | sql = getattr(
69 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
70 | )()
71 |
72 | self.assertEqual(to_sql, sql)
73 |
74 | def test_update_null(self):
75 | to_sql = self.builder.update({"name": None}, dry=True).to_sql()
76 | print(to_sql)
77 |
78 | sql = """UPDATE "users" SET "name" = \'None\'"""
79 |
80 | self.assertEqual(to_sql, sql)
81 |
82 |
83 | class TestPostgresUpdateGrammar(BaseTestCaseUpdateGrammar, unittest.TestCase):
84 | grammar = "postgres"
85 |
86 | def can_compile_update(self):
87 | """
88 | builder.where('name', 'bob').update({
89 | 'name': 'Joe'
90 | }).to_sql()
91 | """
92 | return """UPDATE "users" SET "name" = 'Joe' WHERE "name" = 'bob'"""
93 |
94 | def raw_expression(self):
95 | """
96 | builder.where('name', 'bob').update({
97 | 'name': 'Joe'
98 | }).to_sql()
99 | """
100 | return """UPDATE "users" SET "name" = "username\""""
101 |
102 | def can_compile_multiple_update(self):
103 | """
104 | self.builder.update({"name": "Joe", "email": "user@email.com"}, dry=True).to_sql()
105 | """
106 | return """UPDATE "users" SET "name" = 'Joe', "email" = 'user@email.com'"""
107 |
108 | def can_compile_update_with_multiple_where(self):
109 | """
110 | builder.where('name', 'bob').where('age', 20).update({
111 | 'name': 'Joe'
112 | }).to_sql()
113 | """
114 | return """UPDATE "users" SET "name" = 'Joe' WHERE "name" = 'bob' AND "age" = '20'"""
115 |
116 | def can_compile_increment(self):
117 | """
118 | builder.increment('age').to_sql()
119 | """
120 | return """UPDATE "users" SET "age" = "age" + '1'"""
121 |
122 | def can_compile_decrement(self):
123 | """
124 | builder.decrement('age', 20).to_sql()
125 | """
126 | return """UPDATE "users" SET "age" = "age" - '20'"""
127 |
--------------------------------------------------------------------------------
/tests/seeds/test_seeds.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from databases.seeds.user_table_seeder import UserTableSeeder
4 | from src.masoniteorm.seeds import Seeder
5 |
6 |
7 | class TestSeeds(unittest.TestCase):
8 | def test_can_run_seeds(self):
9 | seeder = Seeder(dry=True)
10 | seeder.call(UserTableSeeder)
11 |
12 | self.assertEqual(seeder.ran_seeds, [UserTableSeeder])
13 |
--------------------------------------------------------------------------------
/tests/sqlite/builder/test_sqlite_builder_insert.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from tests.integrations.config.database import DATABASES
5 | from src.masoniteorm.connections import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import SQLiteGrammar
9 |
10 |
11 | class User(Model):
12 | __connection__ = "dev"
13 | __timestamps__ = False
14 | pass
15 |
16 |
17 | class BaseTestQueryRelationships(unittest.TestCase):
18 | maxDiff = None
19 |
20 | def get_builder(self, table="users"):
21 | connection = ConnectionFactory().make("sqlite")
22 | return QueryBuilder(
23 | grammar=SQLiteGrammar,
24 | connection_class=connection,
25 | connection="dev",
26 | table=table,
27 | connection_details=DATABASES,
28 | ).on("dev")
29 |
30 | def test_insert(self):
31 | builder = self.get_builder()
32 | result = builder.create(
33 | {"name": "Joe", "email": "joe@masoniteproject.com", "password": "secret"}
34 | )
35 |
36 | self.assertIsInstance(result["id"], int)
37 |
--------------------------------------------------------------------------------
/tests/sqlite/builder/test_sqlite_builder_pagination.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from tests.integrations.config.database import DATABASES
5 | from src.masoniteorm.connections import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import SQLiteGrammar
9 |
10 |
11 | class User(Model):
12 | __connection__ = "dev"
13 |
14 |
15 | class BaseTestQueryRelationships(unittest.TestCase):
16 | maxDiff = None
17 |
18 | def get_builder(self, table="users", model=User()):
19 | connection = ConnectionFactory().make("sqlite")
20 | return QueryBuilder(
21 | grammar=SQLiteGrammar,
22 | connection_class=connection,
23 | connection="dev",
24 | table=table,
25 | model=model,
26 | connection_details=DATABASES,
27 | ).on("dev")
28 |
29 | def test_pagination(self):
30 | builder = self.get_builder()
31 |
32 | paginator = builder.table("users").paginate(1)
33 |
34 | self.assertTrue(paginator.count)
35 | self.assertTrue(paginator.serialize()["data"])
36 | self.assertTrue(paginator.serialize()["meta"])
37 | self.assertTrue(paginator.result)
38 | self.assertTrue(paginator.current_page)
39 | self.assertTrue(paginator.per_page)
40 | self.assertTrue(paginator.count)
41 | self.assertTrue(paginator.last_page)
42 | self.assertTrue(paginator.next_page)
43 | self.assertEqual(paginator.previous_page, None)
44 | self.assertTrue(paginator.total)
45 | for user in paginator:
46 | self.assertIsInstance(user, User)
47 |
48 | paginator = builder.table("users").simple_paginate(10, 1)
49 |
50 | self.assertIsInstance(paginator.to_json(), str)
51 |
52 | self.assertTrue(paginator.count)
53 | self.assertTrue(paginator.serialize()["data"])
54 | self.assertTrue(paginator.serialize()["meta"])
55 | self.assertTrue(paginator.result)
56 | self.assertTrue(paginator.current_page)
57 | self.assertTrue(paginator.per_page)
58 | self.assertTrue(paginator.count)
59 | self.assertEqual(paginator.next_page, None)
60 | self.assertEqual(paginator.previous_page, None)
61 | for user in paginator:
62 | self.assertIsInstance(user, User)
63 |
64 | self.assertIsInstance(paginator.to_json(), str)
65 |
--------------------------------------------------------------------------------
/tests/sqlite/builder/test_sqlite_query_builder_eager_loading.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from tests.integrations.config.database import DATABASES
5 | from src.masoniteorm.connections import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import SQLiteGrammar
9 | from src.masoniteorm.relationships import belongs_to, has_many
10 |
11 |
12 | class Logo(Model):
13 | __connection__ = "dev"
14 |
15 |
16 | class Article(Model):
17 | __connection__ = "dev"
18 |
19 | @belongs_to("id", "article_id")
20 | def logo(self):
21 | return Logo
22 |
23 | @belongs_to("user_id", "id")
24 | def user(self):
25 | return User
26 |
27 |
28 | class Profile(Model):
29 | __connection__ = "dev"
30 |
31 |
32 | class User(Model):
33 | __connection__ = "dev"
34 |
35 | __with__ = ["articles.logo"]
36 |
37 | @has_many("id", "user_id")
38 | def articles(self):
39 | return Article
40 |
41 | @belongs_to("id", "user_id")
42 | def profile(self):
43 | return Profile
44 |
45 |
46 | class EagerUser(Model):
47 | __connection__ = "dev"
48 |
49 | __with__ = ("profile",)
50 | __table__ = "users"
51 |
52 | @belongs_to("id", "user_id")
53 | def profile(self):
54 | return Profile
55 |
56 |
57 | class BaseTestQueryRelationships(unittest.TestCase):
58 | maxDiff = None
59 |
60 | def get_builder(self, table="users", model=User()):
61 | connection = ConnectionFactory().make("sqlite")
62 | return QueryBuilder(
63 | grammar=SQLiteGrammar,
64 | connection="dev",
65 | table=table,
66 | model=model,
67 | connection_details=DATABASES,
68 | ).on("dev")
69 |
70 | def test_with(self):
71 | builder = self.get_builder()
72 | result = builder.with_("profile").get()
73 | for model in result:
74 | if model.profile:
75 | self.assertEqual(model.profile.title, "title")
76 |
77 | def test_with_from_model(self):
78 | builder = EagerUser
79 | result = builder.get()
80 | for model in result:
81 | if model.profile:
82 | self.assertEqual(model.profile.title, "title")
83 |
84 | def test_with_first(self):
85 | builder = self.get_builder()
86 | result = builder.with_("profile").where("id", 1).first()
87 | self.assertEqual(result.profile.title, "title")
88 |
89 | def test_with_where_no_relation(self):
90 | builder = self.get_builder()
91 | result = builder.with_("profile").where("id", 5).first()
92 | result.serialize()
93 |
94 | def test_with_multiple_per_same_relation(self):
95 | builder = self.get_builder()
96 | result = User.with_("articles", "articles.logo").where("id", 1).first()
97 | self.assertTrue(result.serialize()["articles"])
98 | self.assertTrue(result.serialize()["articles"][0]["logo"])
99 |
--------------------------------------------------------------------------------
/tests/sqlite/builder/test_sqlite_transaction.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from tests.integrations.config.database import DATABASES
4 | from src.masoniteorm.connections import ConnectionFactory
5 | from src.masoniteorm.models import Model
6 | from src.masoniteorm.query import QueryBuilder
7 | from src.masoniteorm.query.grammars import SQLiteGrammar
8 | from tests.integrations.config.database import DB
9 | from src.masoniteorm.collection import Collection
10 |
11 |
12 | class User(Model):
13 | __connection__ = "dev"
14 | __timestamps__ = False
15 |
16 |
17 | class BaseTestQueryRelationships(unittest.TestCase):
18 | maxDiff = None
19 |
20 | def get_builder(self, table="users"):
21 | connection = ConnectionFactory().make("sqlite")
22 | return QueryBuilder(
23 | grammar=SQLiteGrammar,
24 | connection="dev",
25 | table=table,
26 | model=User(),
27 | connection_details=DATABASES,
28 | ).on("dev")
29 |
30 | def test_transaction(self):
31 | builder = self.get_builder()
32 | builder.begin()
33 | builder.create({"name": "phillip3", "email": "phillip3"})
34 | user = builder.where("name", "phillip3").first()
35 | self.assertEqual(user["name"], "phillip3")
36 | builder.rollback()
37 | user = builder.where("name", "phillip3").first()
38 | self.assertEqual(user, None)
39 |
40 | def test_transaction_globally(self):
41 | connection = DB.begin_transaction("dev")
42 | self.assertEqual(connection, self.get_builder().new_connection())
43 | DB.commit("dev")
44 | DB.begin_transaction("dev")
45 | DB.rollback("dev")
46 |
47 | def test_chunking(self):
48 | for users in self.get_builder().chunk(10):
49 | self.assertIsInstance(users, Collection)
50 |
--------------------------------------------------------------------------------
/tests/sqlite/grammar/test_sqlite_delete_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import SQLiteGrammar
6 |
7 |
8 | class BaseDeleteGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(SQLiteGrammar, table="users")
11 |
12 | def test_can_compile_delete(self):
13 | to_sql = self.builder.delete("id", 1, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_delete_in(self):
21 | to_sql = self.builder.delete("id", [1, 2, 3], query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_delete_with_where(self):
29 | to_sql = (
30 | self.builder.where("age", 20)
31 | .where("profile", 1)
32 | .delete(query=True)
33 | .to_sql()
34 | )
35 |
36 | sql = getattr(
37 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
38 | )()
39 | self.assertEqual(to_sql, sql)
40 |
41 |
42 | class TestSqliteDeleteGrammar(BaseDeleteGrammarTest, unittest.TestCase):
43 | grammar = "sqlite"
44 |
45 | def can_compile_delete(self):
46 | """
47 | (
48 | self.builder
49 | .delete('id', 1)
50 | .to_sql()
51 | )
52 | """
53 | return """DELETE FROM "users" WHERE "id" = '1'"""
54 |
55 | def can_compile_delete_in(self):
56 | """
57 | (
58 | self.builder
59 | .delete('id', 1)
60 | .to_sql()
61 | )
62 | """
63 | return """DELETE FROM "users" WHERE "id" IN ('1','2','3')"""
64 |
65 | def can_compile_delete_with_where(self):
66 | """
67 | (
68 | self.builder
69 | .where('age', 20)
70 | .where('profile', 1)
71 | .delete()
72 | .to_sql()
73 | )
74 | """
75 | return """DELETE FROM "users" WHERE "age" = '20' AND "profile" = '1'"""
76 |
--------------------------------------------------------------------------------
/tests/sqlite/grammar/test_sqlite_insert_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import SQLiteGrammar
6 |
7 |
8 | class BaseInsertGrammarTest:
9 | def setUp(self):
10 | self.builder = QueryBuilder(SQLiteGrammar, table="users")
11 |
12 | def test_can_compile_insert(self):
13 | to_sql = self.builder.create({"name": "Joe"}, query=True).to_sql()
14 |
15 | sql = getattr(
16 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
17 | )()
18 | self.assertEqual(to_sql, sql)
19 |
20 | def test_can_compile_insert_with_keywords(self):
21 | to_sql = self.builder.create(name="Joe", query=True).to_sql()
22 |
23 | sql = getattr(
24 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
25 | )()
26 | self.assertEqual(to_sql, sql)
27 |
28 | def test_can_compile_bulk_create(self):
29 | to_sql = self.builder.bulk_create(
30 | # These keys are intentionally out of order to show column to value alignment works
31 | [
32 | {"name": "Joe", "age": 5},
33 | {"age": 35, "name": "Bill"},
34 | {"name": "John", "age": 10},
35 | ],
36 | query=True,
37 | ).to_sql()
38 |
39 | sql = getattr(
40 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
41 | )()
42 | self.assertEqual(to_sql, sql)
43 |
44 | def test_can_compile_bulk_create_qmark(self):
45 | to_sql = self.builder.bulk_create(
46 | [{"name": "Joe"}, {"name": "Bill"}, {"name": "John"}], query=True
47 | ).to_qmark()
48 |
49 | sql = getattr(
50 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
51 | )()
52 | self.assertEqual(to_sql, sql)
53 |
54 | def test_can_compile_bulk_create_multiple(self):
55 | to_sql = self.builder.bulk_create(
56 | [
57 | {"name": "Joe", "active": "1"},
58 | {"name": "Bill", "active": "1"},
59 | {"name": "John", "active": "1"},
60 | ],
61 | query=True,
62 | ).to_sql()
63 |
64 | sql = getattr(
65 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
66 | )()
67 | self.assertEqual(to_sql, sql)
68 |
69 |
70 | class TestSqliteUpdateGrammar(BaseInsertGrammarTest, unittest.TestCase):
71 | grammar = "sqlite"
72 |
73 | def can_compile_insert(self):
74 | """
75 | self.builder.create({
76 | 'name': 'Joe'
77 | }).to_sql()
78 | """
79 | return """INSERT INTO "users" ("name") VALUES ('Joe')"""
80 |
81 | def can_compile_insert_with_keywords(self):
82 | """
83 | self.builder.create(name="Joe").to_sql()
84 | """
85 | return """INSERT INTO "users" ("name") VALUES ('Joe')"""
86 |
87 | def can_compile_bulk_create(self):
88 | """
89 | self.builder.create(name="Joe").to_sql()
90 | """
91 | return """INSERT INTO "users" ("age", "name") VALUES ('5', 'Joe'), ('35', 'Bill'), ('10', 'John')"""
92 |
93 | def can_compile_bulk_create_multiple(self):
94 | """
95 | self.builder.create(name="Joe").to_sql()
96 | """
97 | return """INSERT INTO "users" ("active", "name") VALUES ('1', 'Joe'), ('1', 'Bill'), ('1', 'John')"""
98 |
99 | def can_compile_bulk_create_qmark(self):
100 | """
101 | self.builder.create(name="Joe").to_sql()
102 | """
103 | return """INSERT INTO "users" ("name") VALUES ('?'), ('?'), ('?')"""
104 |
--------------------------------------------------------------------------------
/tests/sqlite/grammar/test_sqlite_update_grammar.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from src.masoniteorm.query import QueryBuilder
5 | from src.masoniteorm.query.grammars import SQLiteGrammar
6 | from src.masoniteorm.expressions import Raw
7 |
8 |
9 | class BaseTestCaseUpdateGrammar:
10 | def setUp(self):
11 | self.builder = QueryBuilder(SQLiteGrammar, table="users")
12 |
13 | def test_can_compile_update(self):
14 | to_sql = (
15 | self.builder.where("name", "bob").update({"name": "Joe"}, dry=True).to_sql()
16 | )
17 |
18 | sql = getattr(
19 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
20 | )()
21 | self.assertEqual(to_sql, sql)
22 |
23 | def test_can_compile_multiple_update(self):
24 | to_sql = self.builder.update(
25 | {"name": "Joe", "email": "user@email.com"}, dry=True
26 | ).to_sql()
27 |
28 | sql = getattr(
29 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
30 | )()
31 | self.assertEqual(to_sql, sql)
32 |
33 | def test_can_compile_update_with_multiple_where(self):
34 | to_sql = (
35 | self.builder.where("name", "bob")
36 | .where("age", 20)
37 | .update({"name": "Joe"}, dry=True)
38 | .to_sql()
39 | )
40 |
41 | sql = getattr(
42 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
43 | )()
44 | self.assertEqual(to_sql, sql)
45 |
46 | # def test_can_compile_increment(self):
47 | # to_sql = self.builder.increment("age")
48 | # print(to_sql)
49 |
50 | # self.assertTrue(to_sql.isnumeric())
51 |
52 | # def test_can_compile_decrement(self):
53 | # to_sql = self.builder.decrement("age", 20).to_sql()
54 |
55 | # sql = getattr(
56 | # self, inspect.currentframe().f_code.co_name.replace("test_", "")
57 | # )()
58 | # self.assertEqual(to_sql, sql)
59 |
60 | def test_raw_expression(self):
61 | to_sql = self.builder.update({"name": Raw('"username"')}, dry=True).to_sql()
62 |
63 | sql = getattr(
64 | self, inspect.currentframe().f_code.co_name.replace("test_", "")
65 | )()
66 |
67 | self.assertEqual(to_sql, sql)
68 |
69 |
70 | class TestSqliteUpdateGrammar(BaseTestCaseUpdateGrammar, unittest.TestCase):
71 | grammar = "sqlite"
72 |
73 | def can_compile_update(self):
74 | """
75 | builder.where('name', 'bob').update({
76 | 'name': 'Joe'
77 | }).to_sql()
78 | """
79 | return """UPDATE "users" SET "name" = 'Joe' WHERE "name" = 'bob'"""
80 |
81 | def raw_expression(self):
82 | """
83 | builder.where('name', 'bob').update({
84 | 'name': 'Joe'
85 | }).to_sql()
86 | """
87 | return 'UPDATE "users" SET "name" = "username"'
88 |
89 | def can_compile_multiple_update(self):
90 | """
91 | self.builder.update({"name": "Joe", "email": "user@email.com"}, dry=True).to_sql()
92 | """
93 | return """UPDATE "users" SET "name" = 'Joe', "email" = 'user@email.com'"""
94 |
95 | def can_compile_update_with_multiple_where(self):
96 | """
97 | builder.where('name', 'bob').where('age', 20).update({
98 | 'name': 'Joe'
99 | }).to_sql()
100 | """
101 | return """UPDATE "users" SET "name" = 'Joe' WHERE "name" = 'bob' AND "age" = '20'"""
102 |
103 | def can_compile_increment(self):
104 | """
105 | builder.increment('age').to_sql()
106 | """
107 | return """UPDATE "users" SET "age" = "age" + '1'"""
108 |
109 | def can_compile_decrement(self):
110 | """
111 | builder.decrement('age', 20).to_sql()
112 | """
113 | return """UPDATE "users" SET "age" = "age" - '20'"""
114 |
--------------------------------------------------------------------------------
/tests/sqlite/models/test_observers.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import unittest
3 |
4 | from tests.integrations.config.database import DATABASES
5 | from src.masoniteorm.connections import ConnectionFactory
6 | from src.masoniteorm.models import Model
7 | from src.masoniteorm.query import QueryBuilder
8 | from src.masoniteorm.query.grammars import SQLiteGrammar
9 | from src.masoniteorm.relationships import belongs_to
10 | from tests.utils import MockConnectionFactory
11 | from tests.integrations.config.database import DB
12 |
13 |
14 | class TestM:
15 | pass
16 |
17 |
18 | class UserObserver:
19 | def created(self, user):
20 | TestM.observed_created = 1
21 |
22 | def creating(self, user):
23 | TestM.observed_creating = 1
24 |
25 | def saving(self, user):
26 | TestM.observed_saving = 1
27 |
28 | def saved(self, user):
29 | TestM.observed_saved = 1
30 |
31 | def updating(self, user):
32 | TestM.observed_updating = 1
33 |
34 | def updated(self, user):
35 | TestM.observed_updated = 1
36 |
37 | def booted(self, user):
38 | TestM.observed_booting = 1
39 |
40 | def booting(self, user):
41 | TestM.observed_booted = 1
42 |
43 | def hydrating(self, user):
44 | TestM.observed_hydrating = 1
45 |
46 | def hydrated(self, user):
47 | TestM.observed_hydrated = 1
48 |
49 | def deleting(self, user):
50 | TestM.observed_deleting = 1
51 |
52 | def deleted(self, user):
53 | TestM.observed_deleted = 1
54 |
55 |
56 | class Observer(Model):
57 | __connection__ = "dev"
58 | __timestamps__ = False
59 | __observers__ = {}
60 |
61 |
62 | Observer.observe(UserObserver())
63 |
64 |
65 | class BaseTestQueryRelationships(unittest.TestCase):
66 | maxDiff = None
67 |
68 | def test_created_is_observed(self):
69 | # DB.begin_transaction("dev")
70 | user = Observer.create({"name": "joe"})
71 | self.assertEqual(TestM.observed_creating, 1)
72 | self.assertEqual(TestM.observed_created, 1)
73 | # DB.rollback("dev")
74 |
75 | def test_saving_is_observed(self):
76 | # DB.begin_transaction("dev")
77 | user = Observer.hydrate({"id": 1, "name": "joe"})
78 |
79 | user.name = "bill"
80 | user.save()
81 |
82 | self.assertEqual(TestM.observed_saving, 1)
83 | self.assertEqual(TestM.observed_saved, 1)
84 | # DB.rollback("dev")
85 |
86 | def test_updating_is_observed(self):
87 | # DB.begin_transaction("dev")
88 | user = Observer.hydrate({"id": 1, "name": "joe"})
89 |
90 | re = user.update({"name": "bill"})
91 |
92 | self.assertEqual(TestM.observed_updated, 1)
93 | self.assertEqual(TestM.observed_updating, 1)
94 | # DB.rollback("dev")
95 |
96 | def test_booting_is_observed(self):
97 | # DB.begin_transaction("dev")
98 | user = Observer.hydrate({"id": 1, "name": "joe"})
99 |
100 | re = user.update({"name": "bill"})
101 |
102 | self.assertEqual(TestM.observed_booting, 1)
103 | self.assertEqual(TestM.observed_booted, 1)
104 | # DB.rollback("dev")
105 |
106 | def test_deleting_is_observed(self):
107 | DB.begin_transaction("dev")
108 | user = Observer.hydrate({"id": 10, "name": "joe"})
109 |
110 | re = user.delete()
111 |
112 | self.assertEqual(TestM.observed_deleting, 1)
113 | self.assertEqual(TestM.observed_deleted, 1)
114 | DB.rollback("dev")
115 |
116 | def test_hydrating_is_observed(self):
117 | DB.begin_transaction("dev")
118 | user = Observer.hydrate({"id": 10, "name": "joe"})
119 |
120 | self.assertEqual(TestM.observed_hydrating, 1)
121 | self.assertEqual(TestM.observed_hydrated, 1)
122 | DB.rollback("dev")
123 |
--------------------------------------------------------------------------------
/tests/sqlite/relationships/test_sqlite_polymorphic.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from src.masoniteorm.models import Model
5 | from src.masoniteorm.relationships import belongs_to, has_many, morph_to
6 | from tests.integrations.config.database import DB
7 |
8 |
9 | class Profile(Model):
10 | __table__ = "profiles"
11 | __connection__ = "dev"
12 |
13 |
14 | class Articles(Model):
15 | __table__ = "articles"
16 | __connection__ = "dev"
17 |
18 | @belongs_to("id", "article_id")
19 | def logo(self):
20 | return Logo
21 |
22 |
23 | class Logo(Model):
24 | __table__ = "logos"
25 | __connection__ = "dev"
26 |
27 |
28 | class Like(Model):
29 | __connection__ = "dev"
30 |
31 | @morph_to("record_type", "record_id")
32 | def record(self):
33 | return self
34 |
35 |
36 | class User(Model):
37 | __connection__ = "dev"
38 |
39 | _eager_loads = ()
40 |
41 |
42 | DB.morph_map({"user": User, "article": Articles})
43 |
44 |
45 | class TestRelationships(unittest.TestCase):
46 | maxDiff = None
47 |
48 | def test_can_get_polymorphic_relation(self):
49 | likes = Like.get()
50 | for like in likes:
51 | self.assertIsInstance(like.record, (Articles, User))
52 |
53 | def test_can_get_eager_load_polymorphic_relation(self):
54 | likes = Like.with_("record").get()
55 | for like in likes:
56 | self.assertIsInstance(like.record, (Articles, User))
57 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | from unittest import mock
2 |
3 | from src.masoniteorm.connections.ConnectionFactory import ConnectionFactory
4 | from src.masoniteorm.connections.MySQLConnection import MySQLConnection
5 | from src.masoniteorm.connections.SQLiteConnection import SQLiteConnection
6 | from src.masoniteorm.schema.platforms import MySQLPlatform
7 |
8 |
9 | class MockMySQLConnection(MySQLConnection):
10 | def make_connection(self):
11 | self._connection = mock.MagicMock()
12 | self._cursor = mock.MagicMock()
13 |
14 | return self
15 |
16 | @classmethod
17 | def get_default_platform(cls):
18 | return MySQLPlatform
19 |
20 |
21 | class MockMSSQLConnection(MySQLConnection):
22 | def make_connection(self):
23 | self._connection = mock.MagicMock()
24 | self._cursor = mock.MagicMock()
25 |
26 | return self
27 |
28 | @classmethod
29 | def get_default_platform(cls):
30 | return MySQLPlatform
31 |
32 |
33 | class MockPostgresConnection(MySQLConnection):
34 | def make_connection(self):
35 | self._connection = mock.MagicMock()
36 |
37 | return self
38 |
39 |
40 | class MockSQLiteConnection(SQLiteConnection):
41 | def make_connection(self):
42 | self._connection = mock.MagicMock()
43 |
44 | return self
45 |
46 | def query(self, *args, **kwargs):
47 | return {}
48 |
49 |
50 | class MockConnectionFactory(ConnectionFactory):
51 | _connections = {
52 | "mysql": MockMySQLConnection,
53 | "mssql": MockMSSQLConnection,
54 | "postgres": MockPostgresConnection,
55 | "sqlite": MockSQLiteConnection,
56 | "oracle": "",
57 | }
58 |
--------------------------------------------------------------------------------