├── .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 | Masonite Package 9 | 10 | Python Version 11 | GitHub release (latest by date) 12 | License 13 | Code style: black 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 | --------------------------------------------------------------------------------