masonite-framework-orm 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. masonite_framework_orm-3.0.1.dist-info/METADATA +87 -0
  2. masonite_framework_orm-3.0.1.dist-info/RECORD +116 -0
  3. masonite_framework_orm-3.0.1.dist-info/WHEEL +5 -0
  4. masonite_framework_orm-3.0.1.dist-info/entry_points.txt +3 -0
  5. masonite_framework_orm-3.0.1.dist-info/licenses/LICENSE +21 -0
  6. masonite_framework_orm-3.0.1.dist-info/top_level.txt +1 -0
  7. masoniteorm/__init__.py +1 -0
  8. masoniteorm/collection/Collection.py +605 -0
  9. masoniteorm/collection/__init__.py +1 -0
  10. masoniteorm/commands/CanOverrideConfig.py +16 -0
  11. masoniteorm/commands/CanOverrideOptionsDefault.py +22 -0
  12. masoniteorm/commands/Command.py +6 -0
  13. masoniteorm/commands/Entry.py +43 -0
  14. masoniteorm/commands/MakeMigrationCommand.py +57 -0
  15. masoniteorm/commands/MakeModelCommand.py +78 -0
  16. masoniteorm/commands/MakeModelDocstringCommand.py +37 -0
  17. masoniteorm/commands/MakeObserverCommand.py +54 -0
  18. masoniteorm/commands/MakeSeedCommand.py +54 -0
  19. masoniteorm/commands/MigrateCommand.py +46 -0
  20. masoniteorm/commands/MigrateFreshCommand.py +41 -0
  21. masoniteorm/commands/MigrateRefreshCommand.py +41 -0
  22. masoniteorm/commands/MigrateResetCommand.py +25 -0
  23. masoniteorm/commands/MigrateRollbackCommand.py +26 -0
  24. masoniteorm/commands/MigrateStatusCommand.py +51 -0
  25. masoniteorm/commands/SeedRunCommand.py +35 -0
  26. masoniteorm/commands/ShellCommand.py +205 -0
  27. masoniteorm/commands/__init__.py +18 -0
  28. masoniteorm/commands/stubs/create_migration.stub +20 -0
  29. masoniteorm/commands/stubs/create_seed.stub +9 -0
  30. masoniteorm/commands/stubs/model.stub +9 -0
  31. masoniteorm/commands/stubs/observer.stub +101 -0
  32. masoniteorm/commands/stubs/table_migration.stub +19 -0
  33. masoniteorm/config.py +123 -0
  34. masoniteorm/connections/BaseConnection.py +101 -0
  35. masoniteorm/connections/ConnectionFactory.py +59 -0
  36. masoniteorm/connections/ConnectionResolver.py +132 -0
  37. masoniteorm/connections/MSSQLConnection.py +176 -0
  38. masoniteorm/connections/MySQLConnection.py +232 -0
  39. masoniteorm/connections/PostgresConnection.py +225 -0
  40. masoniteorm/connections/SQLiteConnection.py +179 -0
  41. masoniteorm/connections/__init__.py +6 -0
  42. masoniteorm/exceptions.py +38 -0
  43. masoniteorm/expressions/__init__.py +1 -0
  44. masoniteorm/expressions/expressions.py +288 -0
  45. masoniteorm/factories/Factory.py +112 -0
  46. masoniteorm/factories/__init__.py +1 -0
  47. masoniteorm/helpers/__init__.py +0 -0
  48. masoniteorm/helpers/misc.py +22 -0
  49. masoniteorm/migrations/Migration.py +330 -0
  50. masoniteorm/migrations/__init__.py +1 -0
  51. masoniteorm/models/MigrationModel.py +9 -0
  52. masoniteorm/models/Model.py +1209 -0
  53. masoniteorm/models/Model.pyi +1366 -0
  54. masoniteorm/models/Pivot.py +5 -0
  55. masoniteorm/models/__init__.py +1 -0
  56. masoniteorm/observers/ObservesEvents.py +27 -0
  57. masoniteorm/observers/__init__.py +1 -0
  58. masoniteorm/pagination/BasePaginator.py +10 -0
  59. masoniteorm/pagination/LengthAwarePaginator.py +34 -0
  60. masoniteorm/pagination/SimplePaginator.py +28 -0
  61. masoniteorm/pagination/__init__.py +2 -0
  62. masoniteorm/providers/ORMProvider.py +39 -0
  63. masoniteorm/providers/__init__.py +1 -0
  64. masoniteorm/query/EagerRelation.py +42 -0
  65. masoniteorm/query/QueryBuilder.py +2486 -0
  66. masoniteorm/query/__init__.py +1 -0
  67. masoniteorm/query/grammars/BaseGrammar.py +1027 -0
  68. masoniteorm/query/grammars/MSSQLGrammar.py +194 -0
  69. masoniteorm/query/grammars/MySQLGrammar.py +238 -0
  70. masoniteorm/query/grammars/PostgresGrammar.py +213 -0
  71. masoniteorm/query/grammars/SQLiteGrammar.py +228 -0
  72. masoniteorm/query/grammars/__init__.py +4 -0
  73. masoniteorm/query/processors/MSSQLPostProcessor.py +58 -0
  74. masoniteorm/query/processors/MySQLPostProcessor.py +48 -0
  75. masoniteorm/query/processors/PostgresPostProcessor.py +49 -0
  76. masoniteorm/query/processors/SQLitePostProcessor.py +49 -0
  77. masoniteorm/query/processors/__init__.py +4 -0
  78. masoniteorm/relationships/BaseRelationship.py +161 -0
  79. masoniteorm/relationships/BelongsTo.py +124 -0
  80. masoniteorm/relationships/BelongsToMany.py +604 -0
  81. masoniteorm/relationships/HasMany.py +66 -0
  82. masoniteorm/relationships/HasManyThrough.py +269 -0
  83. masoniteorm/relationships/HasOne.py +111 -0
  84. masoniteorm/relationships/HasOneThrough.py +275 -0
  85. masoniteorm/relationships/MorphMany.py +152 -0
  86. masoniteorm/relationships/MorphOne.py +156 -0
  87. masoniteorm/relationships/MorphTo.py +111 -0
  88. masoniteorm/relationships/MorphToMany.py +108 -0
  89. masoniteorm/relationships/__init__.py +10 -0
  90. masoniteorm/schema/Blueprint.py +1161 -0
  91. masoniteorm/schema/Column.py +144 -0
  92. masoniteorm/schema/ColumnDiff.py +0 -0
  93. masoniteorm/schema/Constraint.py +5 -0
  94. masoniteorm/schema/ForeignKeyConstraint.py +28 -0
  95. masoniteorm/schema/Index.py +5 -0
  96. masoniteorm/schema/Schema.py +359 -0
  97. masoniteorm/schema/Table.py +94 -0
  98. masoniteorm/schema/TableDiff.py +86 -0
  99. masoniteorm/schema/__init__.py +3 -0
  100. masoniteorm/schema/platforms/MSSQLPlatform.py +367 -0
  101. masoniteorm/schema/platforms/MySQLPlatform.py +513 -0
  102. masoniteorm/schema/platforms/Platform.py +97 -0
  103. masoniteorm/schema/platforms/PostgresPlatform.py +551 -0
  104. masoniteorm/schema/platforms/SQLitePlatform.py +481 -0
  105. masoniteorm/schema/platforms/__init__.py +4 -0
  106. masoniteorm/scopes/BaseScope.py +6 -0
  107. masoniteorm/scopes/SoftDeleteScope.py +56 -0
  108. masoniteorm/scopes/SoftDeletesMixin.py +13 -0
  109. masoniteorm/scopes/TimeStampsMixin.py +12 -0
  110. masoniteorm/scopes/TimeStampsScope.py +47 -0
  111. masoniteorm/scopes/UUIDPrimaryKeyMixin.py +8 -0
  112. masoniteorm/scopes/UUIDPrimaryKeyScope.py +51 -0
  113. masoniteorm/scopes/__init__.py +8 -0
  114. masoniteorm/scopes/scope.py +15 -0
  115. masoniteorm/seeds/Seeder.py +42 -0
  116. masoniteorm/seeds/__init__.py +1 -0
@@ -0,0 +1,205 @@
1
+ import os
2
+ import re
3
+ import shlex
4
+ import subprocess
5
+ from collections import OrderedDict
6
+
7
+ from ..config import load_config
8
+ from .Command import Command
9
+
10
+
11
+ class ShellCommand(Command):
12
+ """
13
+ Connect to your database interactive terminal.
14
+
15
+ shell
16
+ {--c|connection=default : The connection you want to use to connect to interactive terminal}
17
+ {--s|show=? : Display the command which will be called to connect}
18
+ """
19
+
20
+ shell_programs = {
21
+ "mysql": "mysql",
22
+ "postgres": "psql",
23
+ "sqlite": "sqlite3",
24
+ "mssql": "sqlcmd",
25
+ }
26
+
27
+ def handle(self):
28
+ resolver = load_config(self.option("config")).DB
29
+ connection = self.option("connection")
30
+ if connection == "default":
31
+ connection = resolver.get_connection_details()["default"]
32
+ config = resolver.get_connection_information(connection)
33
+ if not config.get("full_details"):
34
+ self.line(
35
+ f"<error>Connection configuration for '{connection}' not found !</error>"
36
+ )
37
+ exit(-1)
38
+
39
+ command, env = self.get_command(config)
40
+
41
+ if self.option("show"):
42
+ cleaned_command = self.hide_sensitive_options(config, command)
43
+ self.comment(cleaned_command)
44
+ self.line("")
45
+
46
+ # let shlex split command in a list as it's safer
47
+ command_args = shlex.split(command)
48
+ try:
49
+ subprocess.run(command_args, check=True, env=env)
50
+ except FileNotFoundError:
51
+ self.line(
52
+ f"<error>Cannot find {config.get('full_details').get('driver')} program ! Please ensure you can call this program in your shell first.</error>"
53
+ )
54
+ exit(-1)
55
+ except subprocess.CalledProcessError:
56
+ self.line("<error>An error happened calling the command.</error>")
57
+ exit(-1)
58
+
59
+ def get_shell_program(self, connection):
60
+ """Get the database shell program to run."""
61
+ return self.shell_programs.get(connection)
62
+
63
+ def get_command(self, config):
64
+ """Get the command to run as a string."""
65
+ driver = config.get("full_details").get("driver")
66
+ program = self.get_shell_program(driver)
67
+ try:
68
+ get_driver_args = getattr(self, f"get_{driver}_args")
69
+ except AttributeError:
70
+ self.line(
71
+ f"<error>Connecting with driver '{driver}' is not implemented !</error>"
72
+ )
73
+ exit(-1)
74
+ args, options = get_driver_args(config)
75
+ # process positional arguments
76
+ args = " ".join(args)
77
+ # process optional arguments
78
+ options = self.remove_empty_options(options)
79
+ options_string = " ".join(
80
+ f"{option} {value}" if value else option
81
+ for option, value in options.items()
82
+ )
83
+ # finally build command string
84
+ command = program
85
+ if args:
86
+ command += f" {args}"
87
+ if options_string:
88
+ command += f" {options_string}"
89
+
90
+ # prepare environment in which command will be run
91
+ # some drivers need to define env variable such as psql for specifying password
92
+ try:
93
+ driver_env = getattr(self, f"get_{driver}_env")(config)
94
+ except AttributeError:
95
+ driver_env = {}
96
+ command_env = {**os.environ.copy(), **driver_env}
97
+
98
+ return command, command_env
99
+
100
+ def get_mysql_args(self, config):
101
+ """Get command positional arguments and options for MySQL driver."""
102
+ args = [config.get("database")]
103
+ options = OrderedDict(
104
+ {
105
+ "--host": config.get("host"),
106
+ "--port": config.get("port"),
107
+ "--user": config.get("user"),
108
+ "--password": config.get("password"),
109
+ "--default-character-set": config.get("options", {}).get(
110
+ "charset"
111
+ ),
112
+ }
113
+ )
114
+ return args, options
115
+
116
+ def get_postgres_args(self, config):
117
+ """Get command positional arguments and options for PostgreSQL driver."""
118
+ args = [config.get("database")]
119
+ options = OrderedDict(
120
+ {
121
+ "--host": config.get("host"),
122
+ "--port": config.get("port"),
123
+ "--username": config.get("user"),
124
+ }
125
+ )
126
+ return args, options
127
+
128
+ def get_postgres_env(self, config):
129
+ return {"PGPASSWORD": config.get("password")}
130
+
131
+ def get_mssql_args(self, config):
132
+ """Get command positional arguments and options for MSSQL driver."""
133
+ args = []
134
+
135
+ # instance of SQL Server: -S [protocol:]server[instance_name][,port]
136
+ server = f"tcp:{config.get('host')}"
137
+ if config.get("port"):
138
+ server += f",{config.get('port')}"
139
+
140
+ trusted_connection = (
141
+ config.get("options").get("trusted_connection") == "Yes"
142
+ )
143
+ options = OrderedDict(
144
+ {
145
+ "-d": config.get("database"),
146
+ "-U": config.get("user"),
147
+ "-P": config.get("password"),
148
+ "-S": server,
149
+ "-E": trusted_connection,
150
+ "-t": config.get("options", {}).get("connection_timeout"),
151
+ }
152
+ )
153
+ return args, options
154
+
155
+ def get_sqlite_args(self, config):
156
+ """Get command positional arguments and options for SQLite driver."""
157
+ args = [config.get("database")]
158
+ options = OrderedDict()
159
+ return args, options
160
+
161
+ def remove_empty_options(self, options):
162
+ """Remove empty options when value does not evaluate to True.
163
+ Also when value is exactly 'True' we don't want True to be passed as option value but
164
+ we want the option to be passed.
165
+ """
166
+ # remove empty options and remove value when option is True
167
+ cleaned_options = OrderedDict()
168
+ for key, value in options.items():
169
+ if value is True:
170
+ cleaned_options[key] = ""
171
+ elif value:
172
+ cleaned_options[key] = value
173
+ return cleaned_options
174
+
175
+ def get_sensitive_options(self, config):
176
+ driver = config.get("full_details").get("driver")
177
+ try:
178
+ sensitive_options = getattr(
179
+ self, f"get_{driver}_sensitive_options"
180
+ )()
181
+ except AttributeError:
182
+ sensitive_options = []
183
+ return sensitive_options
184
+
185
+ def get_mysql_sensitive_options(self):
186
+ return ["--password"]
187
+
188
+ def get_mssql_sensitive_options(self):
189
+ return ["-P"]
190
+
191
+ def hide_sensitive_options(self, config, command):
192
+ """Hide sensitive options in command string before it's displayed in the shell for
193
+ security reasons. All drivers can declare what options are considered sensitive, their
194
+ values will be then replaced by *** when displayed only."""
195
+ cleaned_command = command
196
+ sensitive_options = self.get_sensitive_options(config)
197
+ for option in sensitive_options:
198
+ # if option is used obfuscate its value
199
+ if option in command:
200
+ match = re.search(rf"{option} (\w+)", command)
201
+ if match.groups():
202
+ cleaned_command = cleaned_command.replace(
203
+ match.groups()[0], "***"
204
+ )
205
+ return cleaned_command
@@ -0,0 +1,18 @@
1
+ import os
2
+ import sys
3
+
4
+ sys.path.append(os.getcwd())
5
+
6
+ from .MakeMigrationCommand import MakeMigrationCommand
7
+ from .MakeModelCommand import MakeModelCommand
8
+ from .MakeModelDocstringCommand import MakeModelDocstringCommand
9
+ from .MakeObserverCommand import MakeObserverCommand
10
+ from .MakeSeedCommand import MakeSeedCommand
11
+ from .MigrateCommand import MigrateCommand
12
+ from .MigrateFreshCommand import MigrateFreshCommand
13
+ from .MigrateRefreshCommand import MigrateRefreshCommand
14
+ from .MigrateResetCommand import MigrateResetCommand
15
+ from .MigrateRollbackCommand import MigrateRollbackCommand
16
+ from .MigrateStatusCommand import MigrateStatusCommand
17
+ from .SeedRunCommand import SeedRunCommand
18
+ from .ShellCommand import ShellCommand
@@ -0,0 +1,20 @@
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__")
@@ -0,0 +1,9 @@
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
@@ -0,0 +1,9 @@
1
+ """ __CLASS__ Model """
2
+
3
+ from masoniteorm.models import Model
4
+
5
+
6
+ class __CLASS__(Model):
7
+ """__CLASS__ Model"""
8
+
9
+ pass
@@ -0,0 +1,101 @@
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
@@ -0,0 +1,19 @@
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
masoniteorm/config.py ADDED
@@ -0,0 +1,123 @@
1
+ import os
2
+ import pydoc
3
+ import urllib.parse as urlparse
4
+
5
+ from .exceptions import ConfigurationNotFound, InvalidUrlConfiguration
6
+
7
+
8
+ def load_config(config_path=None):
9
+ """Load ORM configuration from given configuration path (dotted or not).
10
+ If no path is provided:
11
+ 1. try to load from DB_CONFIG_PATH environment variable
12
+ 2. else try to load from default config_path: config/database
13
+ """
14
+ selected_config_path = (
15
+ os.getenv("DB_CONFIG_PATH", config_path) or "config/database"
16
+ )
17
+
18
+ os.environ["DB_CONFIG_PATH"] = selected_config_path
19
+
20
+ # format path as python module if needed
21
+ selected_config_path = (
22
+ selected_config_path.replace("/", ".").replace("\\", ".").rstrip(".py")
23
+ )
24
+ config_module = pydoc.locate(selected_config_path)
25
+ if config_module is None:
26
+ raise ConfigurationNotFound(
27
+ f"ORM configuration file has not been found in {selected_config_path}."
28
+ )
29
+ return config_module
30
+
31
+
32
+ def db_url(database_url=None, prefix="", options={}, log_queries=False):
33
+ """Parse connection configuration from database url format. If no url is provided,
34
+ DATABASE_URL environment variable will be used instead.
35
+
36
+ Reference: Code adapted from https://github.com/jacobian/dj-database-url
37
+ """
38
+
39
+ url = database_url or os.getenv("DATABASE_URL")
40
+ if not url:
41
+ raise InvalidUrlConfiguration("Database url is empty !")
42
+
43
+ # Register database schemes in URLs.
44
+ urlparse.uses_netloc.append("postgres")
45
+ urlparse.uses_netloc.append("postgresql")
46
+ urlparse.uses_netloc.append("pgsql")
47
+ urlparse.uses_netloc.append("postgis")
48
+ urlparse.uses_netloc.append("mysql")
49
+ urlparse.uses_netloc.append("mysql2")
50
+ urlparse.uses_netloc.append("mysqlgis")
51
+ urlparse.uses_netloc.append("mssql")
52
+ urlparse.uses_netloc.append("sqlite")
53
+
54
+ DRIVERS_MAP = {
55
+ "postgres": "postgres",
56
+ "postgresql": "postgres",
57
+ "pgsql": "postgres",
58
+ "postgis": "postgres",
59
+ "mysql": "mysql",
60
+ "mysql2": "mysql",
61
+ "mysqlgis": "mysql",
62
+ "mysql-connector": "mysql",
63
+ "mssql": "mssql",
64
+ "sqlite": "sqlite",
65
+ }
66
+
67
+ # this is a special case, because if we pass this URL into
68
+ # urlparse, urlparse will choke trying to interpret "memory"
69
+ # as a port number
70
+ if url in ["sqlite://:memory:", "sqlite://memory"]:
71
+ driver = DRIVERS_MAP["sqlite"]
72
+ path = ":memory:"
73
+ # otherwise parse the url as normal
74
+ else:
75
+ url = urlparse.urlparse(url)
76
+ # remove query string from path (not parsed for now)
77
+ path = url.path[1:]
78
+ if "?" in path and not url.query:
79
+ path, _ = path.split("?", 2)
80
+
81
+ # if we are using sqlite and we have no path, then assume we
82
+ # want an in-memory database (this is the behaviour of sqlalchemy)
83
+ if url.scheme == "sqlite" and path == "":
84
+ path = ":memory:"
85
+
86
+ # handle postgres percent-encoded paths.
87
+ hostname = url.hostname or ""
88
+ if "%2f" in hostname.lower():
89
+ # Switch to url.netloc to avoid lower cased paths
90
+ hostname = url.netloc
91
+ if "@" in hostname:
92
+ hostname = hostname.rsplit("@", 1)[1]
93
+ if ":" in hostname:
94
+ hostname = hostname.split(":", 1)[0]
95
+ hostname = hostname.replace("%2f", "/").replace("%2F", "/")
96
+
97
+ # lookup specified driver
98
+ driver = DRIVERS_MAP[url.scheme]
99
+ port = (
100
+ str(url.port)
101
+ if url.port and driver in [DRIVERS_MAP["mssql"]]
102
+ else url.port
103
+ )
104
+
105
+ # build final configuration
106
+ config = {
107
+ "driver": driver,
108
+ "database": urlparse.unquote(path or ""),
109
+ "prefix": prefix,
110
+ "options": options,
111
+ "log_queries": log_queries,
112
+ }
113
+
114
+ if driver != DRIVERS_MAP["sqlite"]:
115
+ config.update(
116
+ {
117
+ "user": urlparse.unquote(url.username or ""),
118
+ "password": urlparse.unquote(url.password or ""),
119
+ "host": hostname,
120
+ "port": port or "",
121
+ }
122
+ )
123
+ return config
@@ -0,0 +1,101 @@
1
+ import logging
2
+ from timeit import default_timer as timer
3
+
4
+ from .ConnectionResolver import ConnectionResolver
5
+
6
+
7
+ class BaseConnection:
8
+ _connection = None
9
+ _cursor = None
10
+ _dry = False
11
+
12
+ def dry(self):
13
+ self._dry = True
14
+ return self
15
+
16
+ def set_schema(self, schema):
17
+ self.schema = schema
18
+ return self
19
+
20
+ def log(
21
+ self,
22
+ query,
23
+ bindings,
24
+ query_time=0,
25
+ logger="masoniteorm.connections.queries",
26
+ ):
27
+ logger = logging.getLogger("masoniteorm.connection.queries")
28
+ logger.propagate = self.full_details.get("propagate", True)
29
+
30
+ logger.debug(
31
+ f"Running query {query}, {bindings}. Executed in {query_time}ms",
32
+ extra={
33
+ "query": query,
34
+ "bindings": bindings,
35
+ "query_time": query_time,
36
+ },
37
+ )
38
+
39
+ def statement(self, query, bindings=()):
40
+ """Wrapper around calling the cursor query. Helpful for logging output.
41
+
42
+ Args:
43
+ query (string): The query to execute on the cursor
44
+ bindings (tuple, optional): Tuple of query bindings. Defaults to ().
45
+ """
46
+ start = timer()
47
+ if not self._cursor:
48
+ raise AttributeError(
49
+ f"Must set the _cursor attribute on the {self.__class__.__name__} class before calling the 'statement' method."
50
+ )
51
+
52
+ self._cursor.execute(query, bindings)
53
+ end = "{:.2f}".format(timer() - start)
54
+
55
+ if self.full_details and self.full_details.get("log_queries", False):
56
+ self.log(query, bindings, query_time=end)
57
+
58
+ def has_global_connection(self):
59
+ return self.name in ConnectionResolver().get_global_connections()
60
+
61
+ def get_global_connection(self):
62
+ return ConnectionResolver().get_global_connections()[self.name]
63
+
64
+ def enable_query_log(self):
65
+ self.full_details["log_queries"] = True
66
+
67
+ def disable_query_log(self):
68
+ self.full_details["log_queries"] = False
69
+
70
+ def format_cursor_results(self, cursor_result):
71
+ return cursor_result
72
+
73
+ def set_cursor(self):
74
+ self._cursor = self._connection.cursor()
75
+ return self
76
+
77
+ def select_many(self, query, bindings, amount):
78
+ self.set_cursor()
79
+ self.statement(query)
80
+ if not self.open:
81
+ self.make_connection()
82
+
83
+ result = self.format_cursor_results(self._cursor.fetchmany(amount))
84
+ while result:
85
+ yield result
86
+
87
+ result = self.format_cursor_results(self._cursor.fetchmany(amount))
88
+
89
+ def get_row_count(self):
90
+ return self._cursor.rowcount
91
+
92
+ def enable_disable_foreign_keys(self):
93
+ foreign_keys = self.full_details.get("foreign_keys")
94
+ platform = self.get_default_platform()()
95
+
96
+ if foreign_keys:
97
+ self._connection.execute(platform.enable_foreign_key_constraints())
98
+ elif foreign_keys is not None:
99
+ self._connection.execute(
100
+ platform.disable_foreign_key_constraints()
101
+ )
@@ -0,0 +1,59 @@
1
+ from ..config import load_config
2
+ from .ConnectionResolver import ConnectionResolver
3
+
4
+
5
+ class ConnectionFactory:
6
+ """Class for controlling the registration and creation of connection types."""
7
+
8
+ _connections = {}
9
+
10
+ def __init__(self, config_path=None, resolver=None):
11
+ self.config_path = config_path
12
+ self._resolver: ConnectionResolver = resolver
13
+
14
+ @classmethod
15
+ def register(cls, key, connection):
16
+ """Registers new connections
17
+
18
+ Arguments:
19
+ key {key} -- The key or driver name you want assigned to this connection
20
+ connection {masoniteorm.connections.BaseConnection} -- An instance of a BaseConnection class.
21
+
22
+ Returns:
23
+ cls
24
+ """
25
+ cls._connections.update({key: connection})
26
+ return cls
27
+
28
+ def make(self, key):
29
+ """Makes already registered connections
30
+
31
+ Arguments:
32
+ key {string} -- The name of the connection you want to make
33
+
34
+ Raises:
35
+ Exception: Raises exception if there are no driver keys that match
36
+
37
+ Returns:
38
+ masoniteorm.connection.BaseConnection -- Returns an instance of a BaseConnection class.
39
+ """
40
+ if not self._resolver:
41
+ self._resolver = load_config(config_path=self.config_path).DB
42
+
43
+ connections = self._resolver.get_connection_details()
44
+ if key == "default":
45
+ connection_details = connections.get(connections.get("default"))
46
+ connection = self._connections.get(
47
+ connection_details.get("driver")
48
+ )
49
+ else:
50
+ connection = self._connections.get(key)
51
+
52
+ if connection:
53
+ return connection
54
+
55
+ raise Exception(
56
+ "The '{connection}' connection does not exist".format(
57
+ connection=key
58
+ )
59
+ )