square_database_structure 2.8.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 (34) hide show
  1. square_database_structure/__init__.py +1 -0
  2. square_database_structure/create_database.py +108 -0
  3. square_database_structure/main.py +101 -0
  4. square_database_structure/square/__init__.py +1 -0
  5. square_database_structure/square/authentication/__init__.py +1 -0
  6. square_database_structure/square/authentication/data.py +1 -0
  7. square_database_structure/square/authentication/enums.py +17 -0
  8. square_database_structure/square/authentication/stored_procedures_and_functions/__init__.py +10 -0
  9. square_database_structure/square/authentication/tables.py +242 -0
  10. square_database_structure/square/email/__init__.py +1 -0
  11. square_database_structure/square/email/data.py +1 -0
  12. square_database_structure/square/email/enums.py +11 -0
  13. square_database_structure/square/email/stored_procedures_and_functions/__init__.py +10 -0
  14. square_database_structure/square/email/tables.py +57 -0
  15. square_database_structure/square/file_storage/__init__.py +1 -0
  16. square_database_structure/square/file_storage/data.py +1 -0
  17. square_database_structure/square/file_storage/enums.py +0 -0
  18. square_database_structure/square/file_storage/stored_procedures_and_functions/__init__.py +10 -0
  19. square_database_structure/square/file_storage/stored_procedures_and_functions/get_file_count.sql +11 -0
  20. square_database_structure/square/file_storage/tables.py +40 -0
  21. square_database_structure/square/greeting/__init__.py +1 -0
  22. square_database_structure/square/greeting/data.py +1 -0
  23. square_database_structure/square/greeting/enums.py +0 -0
  24. square_database_structure/square/greeting/stored_procedures_and_functions/__init__.py +10 -0
  25. square_database_structure/square/greeting/tables.py +35 -0
  26. square_database_structure/square/public/__init__.py +1 -0
  27. square_database_structure/square/public/data.py +7 -0
  28. square_database_structure/square/public/enums.py +8 -0
  29. square_database_structure/square/public/stored_procedures_and_functions/__init__.py +10 -0
  30. square_database_structure/square/public/stored_procedures_and_functions/get_test_text.sql +12 -0
  31. square_database_structure/square/public/tables.py +42 -0
  32. square_database_structure-2.8.1.dist-info/METADATA +243 -0
  33. square_database_structure-2.8.1.dist-info/RECORD +34 -0
  34. square_database_structure-2.8.1.dist-info/WHEEL +4 -0
@@ -0,0 +1 @@
1
+ from square_database_structure.create_database import create_database_and_tables
@@ -0,0 +1,108 @@
1
+ from psycopg2.errors import DuplicateDatabase
2
+ from sqlalchemy import create_engine, inspect, text
3
+ from sqlalchemy.orm import sessionmaker
4
+
5
+ from square_database_structure.main import global_list_create
6
+
7
+
8
+ def create_database_and_tables(
9
+ db_username: str,
10
+ db_password: str,
11
+ db_ip: str,
12
+ db_port: int,
13
+ drop_if_exists: bool = False,
14
+ ) -> None:
15
+ try:
16
+ local_list_create = global_list_create
17
+
18
+ for local_dict_database in local_list_create:
19
+ local_str_database_name = local_dict_database["database"]
20
+ local_str_postgres_url = (
21
+ f"postgresql://{db_username}:{db_password}@" f"{db_ip}:{str(db_port)}/"
22
+ )
23
+ postgres_engine = create_engine(local_str_postgres_url)
24
+ # Create database if not exists
25
+ try:
26
+ with postgres_engine.connect() as postgres_connection:
27
+ postgres_connection.execute(text("commit"))
28
+ postgres_connection.execute(
29
+ text(f"CREATE DATABASE {local_str_database_name}")
30
+ )
31
+ except Exception as e:
32
+ if isinstance(getattr(e, "orig"), DuplicateDatabase):
33
+ if drop_if_exists:
34
+ with postgres_engine.connect() as postgres_connection:
35
+ postgres_connection.execute(text("commit"))
36
+ postgres_connection.execute(
37
+ text(
38
+ f"DROP DATABASE {local_str_database_name} WITH (FORCE)"
39
+ )
40
+ )
41
+ postgres_connection.execute(
42
+ text(f"CREATE DATABASE {local_str_database_name}")
43
+ )
44
+ else:
45
+ raise
46
+ # ===========================================
47
+ local_str_database_url = (
48
+ f"postgresql://{db_username}:{db_password}@"
49
+ f"{db_ip}:{str(db_port)}/{local_str_database_name}"
50
+ )
51
+ database_engine = create_engine(local_str_database_url)
52
+ with database_engine.connect() as database_connection:
53
+ for local_dict_schema in local_dict_database["schemas"]:
54
+ local_str_schema_name = local_dict_schema["schema"]
55
+ # Create schema if not exists
56
+ if not database_engine.dialect.has_schema(
57
+ database_connection, local_str_schema_name
58
+ ):
59
+ database_connection.execute(text("commit"))
60
+ database_connection.execute(
61
+ text(f"CREATE SCHEMA {local_str_schema_name}")
62
+ )
63
+ else:
64
+ pass
65
+ # ===========================================
66
+ database_connection.execute(
67
+ text(f"SET search_path TO {local_str_schema_name}")
68
+ )
69
+
70
+ inspector = inspect(database_engine)
71
+ existing_table_names = inspector.get_table_names(
72
+ schema=local_str_schema_name
73
+ )
74
+
75
+ base = local_dict_schema["base"]
76
+ # Create tables if not exists
77
+ base.metadata.create_all(database_engine)
78
+ # ===========================================
79
+ data_to_insert = local_dict_schema["data_to_insert"]
80
+ local_object_session = sessionmaker(bind=database_engine)
81
+ session = local_object_session()
82
+ filtered_data_to_insert = [
83
+ x
84
+ for x in data_to_insert
85
+ if x.__tablename__ not in existing_table_names
86
+ ]
87
+ # insert data for newly created tables
88
+ try:
89
+ session.add_all(filtered_data_to_insert)
90
+ # ===========================================
91
+ session.commit()
92
+ session.close()
93
+ except Exception:
94
+ session.rollback()
95
+ session.close()
96
+ raise
97
+ # create stored procedures
98
+ stored_procedures_and_functions = local_dict_schema[
99
+ "stored_procedures_and_functions"
100
+ ]
101
+ for (
102
+ stored_procedures_and_function
103
+ ) in stored_procedures_and_functions:
104
+ database_connection.execute(
105
+ text(stored_procedures_and_function)
106
+ )
107
+ except Exception:
108
+ raise
@@ -0,0 +1,101 @@
1
+ from square_database_structure.square import (
2
+ global_string_database_name as local_string_square_database_name,
3
+ )
4
+ from square_database_structure.square.authentication import (
5
+ global_string_schema_name as local_string_square_authentication_schema_name,
6
+ )
7
+ from square_database_structure.square.authentication.data import (
8
+ data_to_insert as local_list_square_authentication_data_to_insert,
9
+ )
10
+ from square_database_structure.square.authentication.stored_procedures_and_functions import (
11
+ stored_procedures_and_functions as local_list_square_authentication_stored_procedures_and_functions,
12
+ )
13
+ from square_database_structure.square.authentication.tables import (
14
+ Base as SquareAuthenticationBase,
15
+ )
16
+ from square_database_structure.square.email import (
17
+ global_string_schema_name as local_string_square_email_schema_name,
18
+ )
19
+ from square_database_structure.square.email.data import (
20
+ data_to_insert as local_list_square_email_data_to_insert,
21
+ )
22
+ from square_database_structure.square.email.stored_procedures_and_functions import (
23
+ stored_procedures_and_functions as local_list_square_email_stored_procedures_and_functions,
24
+ )
25
+ from square_database_structure.square.email.tables import (
26
+ Base as SquareEmailBase,
27
+ )
28
+ from square_database_structure.square.file_storage import (
29
+ global_string_schema_name as local_string_square_file_storage_schema_name,
30
+ )
31
+ from square_database_structure.square.file_storage.data import (
32
+ data_to_insert as local_list_square_file_storage_data_to_insert,
33
+ )
34
+ from square_database_structure.square.file_storage.stored_procedures_and_functions import (
35
+ stored_procedures_and_functions as local_list_square_file_storage_stored_procedures_and_functions,
36
+ )
37
+ from square_database_structure.square.file_storage.tables import (
38
+ Base as SquareFileStorageBase,
39
+ )
40
+ from square_database_structure.square.greeting import (
41
+ global_string_schema_name as local_string_square_greeting_schema_name,
42
+ )
43
+ from square_database_structure.square.greeting.data import (
44
+ data_to_insert as local_list_square_greeting_data_to_insert,
45
+ )
46
+ from square_database_structure.square.greeting.stored_procedures_and_functions import (
47
+ stored_procedures_and_functions as local_list_square_greeting_stored_procedures_and_functions,
48
+ )
49
+ from square_database_structure.square.greeting.tables import (
50
+ Base as SquareGreetingBase,
51
+ )
52
+ from square_database_structure.square.public import (
53
+ global_string_schema_name as local_string_square_public_schema_name,
54
+ )
55
+ from square_database_structure.square.public.data import (
56
+ data_to_insert as local_list_square_public_data_to_insert,
57
+ )
58
+ from square_database_structure.square.public.stored_procedures_and_functions import (
59
+ stored_procedures_and_functions as local_list_square_public_stored_procedures_and_functions,
60
+ )
61
+ from square_database_structure.square.public.tables import (
62
+ Base as SquarePublicBase,
63
+ )
64
+
65
+ global_list_create = [
66
+ {
67
+ "database": local_string_square_database_name,
68
+ "schemas": [
69
+ {
70
+ "schema": local_string_square_public_schema_name,
71
+ "base": SquarePublicBase,
72
+ "data_to_insert": local_list_square_public_data_to_insert,
73
+ "stored_procedures_and_functions": local_list_square_public_stored_procedures_and_functions,
74
+ },
75
+ {
76
+ "schema": local_string_square_file_storage_schema_name,
77
+ "base": SquareFileStorageBase,
78
+ "data_to_insert": local_list_square_file_storage_data_to_insert,
79
+ "stored_procedures_and_functions": local_list_square_file_storage_stored_procedures_and_functions,
80
+ },
81
+ {
82
+ "schema": local_string_square_authentication_schema_name,
83
+ "base": SquareAuthenticationBase,
84
+ "data_to_insert": local_list_square_authentication_data_to_insert,
85
+ "stored_procedures_and_functions": local_list_square_authentication_stored_procedures_and_functions,
86
+ },
87
+ {
88
+ "schema": local_string_square_greeting_schema_name,
89
+ "base": SquareGreetingBase,
90
+ "data_to_insert": local_list_square_greeting_data_to_insert,
91
+ "stored_procedures_and_functions": local_list_square_greeting_stored_procedures_and_functions,
92
+ },
93
+ {
94
+ "schema": local_string_square_email_schema_name,
95
+ "base": SquareEmailBase,
96
+ "data_to_insert": local_list_square_email_data_to_insert,
97
+ "stored_procedures_and_functions": local_list_square_email_stored_procedures_and_functions,
98
+ },
99
+ ],
100
+ }
101
+ ]
@@ -0,0 +1 @@
1
+ global_string_database_name = "square"
@@ -0,0 +1 @@
1
+ global_string_schema_name = "authentication"
@@ -0,0 +1 @@
1
+ data_to_insert = []
@@ -0,0 +1,17 @@
1
+ from enum import Enum
2
+
3
+
4
+ class RecoveryMethodEnum(Enum):
5
+ EMAIL = "EMAIL"
6
+ BACKUP_CODE = "BACKUP_CODE"
7
+
8
+
9
+ class VerificationCodeTypeEnum(Enum):
10
+ EMAIL_VERIFICATION = "EMAIL_VERIFICATION"
11
+ EMAIL_RECOVERY = "EMAIL_RECOVERY"
12
+ BACKUP_CODE_RECOVERY = "BACKUP_CODE_RECOVERY"
13
+
14
+
15
+ class AuthProviderEnum(Enum):
16
+ SELF = "SELF"
17
+ GOOGLE = "GOOGLE"
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ directory = Path(__file__).parent
4
+ stored_procedures_and_functions = []
5
+
6
+ for file_path in directory.iterdir():
7
+ if file_path.is_file() and file_path.suffix == ".sql":
8
+ with file_path.open("r") as file:
9
+ content = file.read()
10
+ stored_procedures_and_functions.append(content)
@@ -0,0 +1,242 @@
1
+ from sqlalchemy import (
2
+ Column,
3
+ Integer,
4
+ MetaData,
5
+ DateTime,
6
+ text,
7
+ String,
8
+ ForeignKey,
9
+ Enum,
10
+ UniqueConstraint,
11
+ func,
12
+ )
13
+ from sqlalchemy.dialects.postgresql import UUID
14
+ from sqlalchemy.orm import declarative_base
15
+
16
+ from square_database_structure.square.authentication import global_string_schema_name
17
+ from square_database_structure.square.authentication.enums import (
18
+ RecoveryMethodEnum,
19
+ VerificationCodeTypeEnum,
20
+ AuthProviderEnum,
21
+ )
22
+ from square_database_structure.square.file_storage.tables import File
23
+ from square_database_structure.square.public.tables import App
24
+
25
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
26
+
27
+
28
+ class User(Base):
29
+ __tablename__ = "user"
30
+
31
+ user_id = Column(
32
+ UUID,
33
+ primary_key=True,
34
+ nullable=False,
35
+ unique=True,
36
+ server_default=text("gen_random_uuid()"),
37
+ )
38
+ user_username = Column(String, nullable=False, unique=True, index=True)
39
+
40
+
41
+ class UserAuthProvider(Base):
42
+ __tablename__ = "user_auth_provider"
43
+
44
+ user_auth_provider_id = Column(
45
+ Integer, primary_key=True, nullable=False, unique=True, autoincrement=True
46
+ )
47
+ user_id = Column(
48
+ UUID,
49
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
50
+ nullable=False,
51
+ )
52
+ auth_provider = Column(
53
+ Enum(AuthProviderEnum, schema=global_string_schema_name),
54
+ nullable=False,
55
+ )
56
+ auth_provider_user_id = Column(String, nullable=True)
57
+ __table_args__ = (
58
+ UniqueConstraint(
59
+ "user_id",
60
+ "auth_provider",
61
+ name="uq_user_id_auth_provider",
62
+ ),
63
+ UniqueConstraint(
64
+ "auth_provider",
65
+ "auth_provider_user_id",
66
+ name="uq_auth_provider_auth_provider_user_id",
67
+ ),
68
+ )
69
+
70
+
71
+ class UserCredential(Base):
72
+ __tablename__ = "user_credential"
73
+
74
+ user_credential_id = Column(
75
+ Integer, primary_key=True, nullable=False, unique=True, autoincrement=True
76
+ )
77
+ user_id = Column(
78
+ UUID,
79
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
80
+ nullable=False,
81
+ unique=True,
82
+ )
83
+ user_credential_hashed_password = Column(String, nullable=False)
84
+
85
+
86
+ class UserApp(Base):
87
+ __tablename__ = "user_app"
88
+
89
+ user_id = Column(
90
+ UUID,
91
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
92
+ nullable=False,
93
+ primary_key=True,
94
+ )
95
+ app_id = Column(
96
+ Integer,
97
+ ForeignKey(App.app_id, ondelete="CASCADE", onupdate="CASCADE"),
98
+ nullable=False,
99
+ primary_key=True,
100
+ )
101
+
102
+
103
+ class UserSession(Base):
104
+ __tablename__ = "user_session"
105
+
106
+ user_session_id = Column(
107
+ Integer, primary_key=True, unique=True, nullable=False, autoincrement=True
108
+ )
109
+ user_id = Column(
110
+ UUID,
111
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
112
+ nullable=False,
113
+ )
114
+ app_id = Column(
115
+ Integer,
116
+ ForeignKey(App.app_id, ondelete="CASCADE", onupdate="CASCADE"),
117
+ nullable=False,
118
+ )
119
+ user_session_refresh_token = Column(
120
+ String,
121
+ nullable=False,
122
+ unique=True,
123
+ )
124
+ user_session_expiry_time = Column(DateTime(timezone=True), nullable=False)
125
+
126
+
127
+ class UserProfile(Base):
128
+ __tablename__ = "user_profile"
129
+
130
+ user_profile_id = Column(
131
+ Integer, primary_key=True, unique=True, nullable=False, autoincrement=True
132
+ )
133
+ user_id = Column(
134
+ UUID,
135
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
136
+ nullable=False,
137
+ unique=True,
138
+ )
139
+ user_profile_photo_storage_token = Column(
140
+ String,
141
+ ForeignKey(File.file_storage_token, ondelete="RESTRICT", onupdate="CASCADE"),
142
+ nullable=True,
143
+ unique=True,
144
+ default=None,
145
+ )
146
+ user_profile_email = Column(
147
+ String,
148
+ nullable=True,
149
+ unique=True,
150
+ default=None,
151
+ )
152
+ user_profile_email_verified = Column(
153
+ DateTime(timezone=True),
154
+ nullable=True,
155
+ default=None,
156
+ )
157
+ user_profile_phone_number_country_code = Column(
158
+ String,
159
+ nullable=True,
160
+ default=None,
161
+ )
162
+ user_profile_phone_number = Column(
163
+ String,
164
+ nullable=True,
165
+ default=None,
166
+ )
167
+ user_profile_first_name = Column(
168
+ String,
169
+ nullable=True,
170
+ default=None,
171
+ )
172
+ user_profile_last_name = Column(
173
+ String,
174
+ nullable=True,
175
+ default=None,
176
+ )
177
+ __table_args__ = (
178
+ UniqueConstraint(
179
+ "user_profile_phone_number_country_code",
180
+ "user_profile_phone_number",
181
+ name="uq_user_profile_phone_number",
182
+ ),
183
+ )
184
+
185
+
186
+ class UserRecoveryMethod(Base):
187
+ __tablename__ = "user_recovery_method"
188
+
189
+ user_recovery_method_id = Column(
190
+ Integer, primary_key=True, unique=True, nullable=False, autoincrement=True
191
+ )
192
+ user_id = Column(
193
+ UUID,
194
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
195
+ nullable=False,
196
+ )
197
+ user_recovery_method_name = Column(
198
+ Enum(RecoveryMethodEnum, schema=global_string_schema_name),
199
+ nullable=False,
200
+ )
201
+ __table_args__ = (
202
+ UniqueConstraint(
203
+ "user_id",
204
+ "user_recovery_method_name",
205
+ name="uq_user_id_user_recovery_method",
206
+ ),
207
+ )
208
+
209
+
210
+ class UserVerificationCode(Base):
211
+ __tablename__ = "user_verification_code"
212
+
213
+ user_verification_code_id = Column(
214
+ Integer, primary_key=True, unique=True, nullable=False, autoincrement=True
215
+ )
216
+ user_id = Column(
217
+ UUID,
218
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
219
+ nullable=False,
220
+ )
221
+ user_verification_code_type = Column(
222
+ Enum(VerificationCodeTypeEnum, schema=global_string_schema_name),
223
+ nullable=False,
224
+ )
225
+ user_verification_code_hash = Column(
226
+ String,
227
+ unique=True,
228
+ nullable=False,
229
+ )
230
+ user_verification_code_created_at = Column(
231
+ DateTime(timezone=True),
232
+ nullable=False,
233
+ server_default=func.now(),
234
+ )
235
+ user_verification_code_expires_at = Column(
236
+ DateTime(timezone=True),
237
+ nullable=True,
238
+ )
239
+ user_verification_code_used_at = Column(
240
+ DateTime(timezone=True),
241
+ nullable=True,
242
+ )
@@ -0,0 +1 @@
1
+ global_string_schema_name = "email"
@@ -0,0 +1 @@
1
+ data_to_insert = []
@@ -0,0 +1,11 @@
1
+ from enum import Enum
2
+
3
+
4
+ class EmailTypeEnum(Enum):
5
+ VERIFY_EMAIL = "VERIFY_EMAIL"
6
+ PASSWORD_RESET = "PASSWORD_RESET"
7
+
8
+
9
+ class EmailStatusEnum(Enum):
10
+ SENT = "SENT"
11
+ FAILED = "FAILED"
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ directory = Path(__file__).parent
4
+ stored_procedures_and_functions = []
5
+
6
+ for file_path in directory.iterdir():
7
+ if file_path.is_file() and file_path.suffix == ".sql":
8
+ with file_path.open("r") as file:
9
+ content = file.read()
10
+ stored_procedures_and_functions.append(content)
@@ -0,0 +1,57 @@
1
+ from sqlalchemy import (
2
+ Column,
3
+ Integer,
4
+ MetaData,
5
+ DateTime,
6
+ String,
7
+ ForeignKey,
8
+ Enum,
9
+ func,
10
+ )
11
+ from sqlalchemy.dialects.postgresql import UUID
12
+ from sqlalchemy.orm import declarative_base
13
+
14
+ from square_database_structure.square.authentication.tables import User
15
+ from square_database_structure.square.email import global_string_schema_name
16
+ from square_database_structure.square.email.enums import EmailTypeEnum, EmailStatusEnum
17
+
18
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
19
+
20
+
21
+ class EmailLog(Base):
22
+ __tablename__ = "email_log"
23
+
24
+ email_log_id = Column(
25
+ Integer, primary_key=True, unique=True, nullable=False, autoincrement=True
26
+ )
27
+ user_id = Column(
28
+ UUID,
29
+ ForeignKey(User.user_id, ondelete="SET NULL", onupdate="CASCADE"),
30
+ nullable=True,
31
+ index=True,
32
+ )
33
+ recipient_email = Column(
34
+ String,
35
+ nullable=False,
36
+ index=True,
37
+ )
38
+ email_type = Column(
39
+ Enum(EmailTypeEnum, schema=global_string_schema_name),
40
+ nullable=False,
41
+ )
42
+ sent_at = Column(
43
+ DateTime(timezone=True),
44
+ nullable=False,
45
+ server_default=func.now(),
46
+ )
47
+ status = Column(
48
+ Enum(EmailStatusEnum, schema=global_string_schema_name),
49
+ nullable=False,
50
+ )
51
+
52
+ third_party_message_id = Column(
53
+ String,
54
+ nullable=True,
55
+ unique=True,
56
+ index=True,
57
+ )
@@ -0,0 +1 @@
1
+ global_string_schema_name = "file_storage"
@@ -0,0 +1 @@
1
+ data_to_insert = []
File without changes
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ directory = Path(__file__).parent
4
+ stored_procedures_and_functions = []
5
+
6
+ for file_path in directory.iterdir():
7
+ if file_path.is_file() and file_path.suffix == ".sql":
8
+ with file_path.open("r") as file:
9
+ content = file.read()
10
+ stored_procedures_and_functions.append(content)
@@ -0,0 +1,11 @@
1
+ CREATE OR REPLACE FUNCTION get_file_count()
2
+ RETURNS INTEGER AS $$
3
+ DECLARE
4
+ file_count INTEGER;
5
+ BEGIN
6
+ SELECT COUNT(*) INTO file_count
7
+ FROM file;
8
+
9
+ RETURN file_count;
10
+ END;
11
+ $$ LANGUAGE plpgsql;
@@ -0,0 +1,40 @@
1
+ from sqlalchemy import (
2
+ Column,
3
+ DateTime,
4
+ Integer,
5
+ String,
6
+ MetaData,
7
+ ForeignKey,
8
+ )
9
+ from sqlalchemy.orm import declarative_base
10
+ from sqlalchemy.sql import func
11
+
12
+ from square_database_structure.square.file_storage import global_string_schema_name
13
+ from square_database_structure.square.public.tables import App
14
+
15
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
16
+
17
+
18
+ class File(Base):
19
+ __tablename__ = "file"
20
+
21
+ file_id = Column(Integer, autoincrement=True, primary_key=True, index=True)
22
+ file_name_with_extension = Column(String, nullable=False)
23
+ file_content_type = Column(String, nullable=False, index=True)
24
+ file_date_created = Column(
25
+ DateTime(timezone=True), server_default=func.now(), nullable=False
26
+ )
27
+ file_last_modified = Column(
28
+ DateTime(timezone=True),
29
+ server_default=func.now(),
30
+ onupdate=func.now(),
31
+ nullable=False,
32
+ )
33
+ file_system_file_name_with_extension = Column(String, nullable=False)
34
+ file_system_relative_path = Column(String, server_default="", nullable=False)
35
+ file_storage_token = Column(String, nullable=False, unique=True, index=True)
36
+ app_id = Column(
37
+ Integer,
38
+ ForeignKey(App.app_id, ondelete="CASCADE", onupdate="CASCADE"),
39
+ nullable=True,
40
+ )
@@ -0,0 +1 @@
1
+ global_string_schema_name = "greeting"
@@ -0,0 +1 @@
1
+ data_to_insert = []
File without changes
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ directory = Path(__file__).parent
4
+ stored_procedures_and_functions = []
5
+
6
+ for file_path in directory.iterdir():
7
+ if file_path.is_file() and file_path.suffix == ".sql":
8
+ with file_path.open("r") as file:
9
+ content = file.read()
10
+ stored_procedures_and_functions.append(content)
@@ -0,0 +1,35 @@
1
+ from sqlalchemy import (
2
+ MetaData,
3
+ Column,
4
+ Integer,
5
+ String,
6
+ Boolean,
7
+ ForeignKey,
8
+ DateTime,
9
+ func,
10
+ )
11
+ from sqlalchemy.dialects.postgresql import UUID
12
+ from sqlalchemy.orm import declarative_base
13
+
14
+ from square_database_structure.square.authentication.tables import User
15
+ from square_database_structure.square.greeting import global_string_schema_name
16
+
17
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
18
+
19
+
20
+ class Greeting(Base):
21
+ __tablename__ = "greeting"
22
+
23
+ greeting_id = Column(
24
+ Integer, primary_key=True, nullable=False, unique=True, autoincrement=True
25
+ )
26
+ greeting_is_anonymous = Column(Boolean, nullable=False)
27
+ greeting_anonymous_sender_name = Column(String)
28
+ user_id = Column(
29
+ UUID,
30
+ ForeignKey(User.user_id, ondelete="CASCADE", onupdate="CASCADE"),
31
+ )
32
+ greeting_text = Column(String)
33
+ greeting_datetime = Column(
34
+ DateTime(timezone=True), server_default=func.now(), nullable=False
35
+ )
@@ -0,0 +1 @@
1
+ global_string_schema_name = "public"
@@ -0,0 +1,7 @@
1
+ from square_database_structure.square.public.tables import App
2
+
3
+ data_to_insert = []
4
+ data_to_insert.append(App(app_name="test"))
5
+ data_to_insert.append(App(app_name="square_admin"))
6
+ data_to_insert.append(App(app_name="ttthree"))
7
+ data_to_insert.append(App(app_name="square_wallet"))
@@ -0,0 +1,8 @@
1
+ import enum
2
+
3
+
4
+ class TestEnumEnum(enum.Enum):
5
+ PENDING = "PENDING"
6
+ RUNNING = "RUNNING"
7
+ COMPLETED = "COMPLETED"
8
+ FAILED = "FAILED"
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ directory = Path(__file__).parent
4
+ stored_procedures_and_functions = []
5
+
6
+ for file_path in directory.iterdir():
7
+ if file_path.is_file() and file_path.suffix == ".sql":
8
+ with file_path.open("r") as file:
9
+ content = file.read()
10
+ stored_procedures_and_functions.append(content)
@@ -0,0 +1,12 @@
1
+ CREATE OR REPLACE FUNCTION get_test_text(input_test_id INTEGER)
2
+ RETURNS TEXT AS $$
3
+ DECLARE
4
+ test_text TEXT;
5
+ BEGIN
6
+ SELECT t.test_text INTO test_text
7
+ FROM test AS t
8
+ WHERE t.test_id = input_test_id;
9
+
10
+ RETURN test_text;
11
+ END;
12
+ $$ LANGUAGE plpgsql;
@@ -0,0 +1,42 @@
1
+ from sqlalchemy import (
2
+ Column,
3
+ Integer,
4
+ String,
5
+ DateTime,
6
+ Boolean,
7
+ Float,
8
+ JSON,
9
+ LargeBinary,
10
+ Enum,
11
+ MetaData,
12
+ )
13
+ from sqlalchemy.orm import declarative_base
14
+
15
+ from square_database_structure.square.public import global_string_schema_name
16
+ from square_database_structure.square.public.enums import TestEnumEnum
17
+
18
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
19
+
20
+
21
+ class Test(Base):
22
+ __tablename__ = "test"
23
+
24
+ test_id = Column(
25
+ Integer, primary_key=True, nullable=False, unique=True, autoincrement=True
26
+ )
27
+ test_text = Column(String, nullable=True, unique=True)
28
+ test_datetime = Column(DateTime, nullable=True)
29
+ test_bool = Column(Boolean, nullable=True)
30
+ test_enum_enum = Column(Enum(TestEnumEnum), nullable=True)
31
+ test_float = Column(Float, nullable=True)
32
+ test_json = Column(JSON, nullable=True)
33
+ test_blob = Column(LargeBinary, nullable=True)
34
+
35
+
36
+ class App(Base):
37
+ __tablename__ = "app"
38
+
39
+ app_id = Column(
40
+ Integer, primary_key=True, nullable=False, unique=True, autoincrement=True
41
+ )
42
+ app_name = Column(String, nullable=False, unique=True)
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.3
2
+ Name: square_database_structure
3
+ Version: 2.8.1
4
+ Summary: database scheme for my personal server.
5
+ Keywords: database,sqlalchemy,utilities
6
+ Author: Parth Mukesh Mangtani
7
+ Author-email: Parth Mukesh Mangtani <thepmsquare@gmail.com>
8
+ License: GPLv3
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Dist: sqlalchemy>=2.0.23
19
+ Requires-Dist: psycopg2-binary>=2.9.9
20
+ Requires-Dist: pytest>=8.0.0 ; extra == 'all'
21
+ Requires-Dist: pytest>=8.0.0 ; extra == 'test'
22
+ Requires-Python: >=3.12
23
+ Project-URL: homepage, https://github.com/thepmsquare/square_database_structure
24
+ Provides-Extra: all
25
+ Provides-Extra: test
26
+ Description-Content-Type: text/markdown
27
+
28
+ # square_database_structure
29
+
30
+ > 📌 versioning: see [CHANGELOG.md](./CHANGELOG.md).
31
+
32
+ ## about
33
+
34
+ python module to define postgresql database schemas using sqlalchemy.
35
+
36
+ ## goals
37
+
38
+ - clear database → schema → table hierarchy
39
+ - reusable (template) across multiple projects
40
+ - single source of truth for schema and data
41
+
42
+ ## installation
43
+
44
+ ```bash
45
+ pip install square_database_structure
46
+ ```
47
+
48
+ ## usage
49
+
50
+ this module organizes database schemas in a standardized folder structure where each top-level folder represents a
51
+ database, and subfolders within it represent schemas. all mandatory components, such as `__init__.py` and tables.py,
52
+ data.py, stored_procedures_and_functions need to follow this structure.
53
+
54
+ ### folder structure
55
+
56
+ ```
57
+ square_database_structure/
58
+ ├───main.py # global definition file (mandatory)
59
+ ├───create_database.py # global database creation file (mandatory)
60
+ └───database1/ # each folder corresponds to a separate database
61
+ ├───__init__.py # contains the global name for the database (mandatory)
62
+ └───schema1/ # each subfolder corresponds to a schema within the database
63
+ ├───__init__.py # contains the global name for the schema (mandatory)
64
+ ├───data.py # contains the data for insertion for the schema (mandatory)
65
+ ├───enums.py # defines enums to be used in the schema (optional)
66
+ ├───tables.py # defines tables of the schema (mandatory)
67
+ └───stored_procedures_and_functions/ # contains stored procedures and functions for the schema (mandatory)
68
+ ├───__init__.py # contains logic to discover sql files (mandatory)
69
+ └───function1.sql # function or stored procedure sql file (optional)
70
+ ```
71
+
72
+ - top-level folders: represent individual databases (e.g., database1).
73
+ - subfolders: represent schemas within each database (e.g., public, schema1).
74
+ - mandatory files:
75
+ - `__init__.py` (both at the database and schema level).
76
+ - tables.py within each schema.
77
+ - data.py within each schema.
78
+ - stored_procedures_and_functions package within each schema.
79
+
80
+ ### defining database and schema names in `__init__.py`
81
+
82
+ each database and schema folder must contain an `__init__.py` file where the database and schema names are defined
83
+ as global variables.
84
+
85
+ #### example for database:
86
+
87
+ ```python
88
+ # database1/__init__.py
89
+
90
+ global_string_database_name = "database1" # mandatory: database name
91
+ ```
92
+
93
+ #### example for schema:
94
+
95
+ ```python
96
+ # database1/schema1/__init__.py
97
+
98
+ global_string_schema_name = "schema1" # mandatory: schema name
99
+ ```
100
+
101
+ ### defining tables in tables.py
102
+
103
+ each schema folder must contain a tables.py file where:
104
+
105
+ - you must declare a Base object tied to the schema.
106
+ - you can define table classes, extending the Base object.
107
+
108
+ #### example tables.py:
109
+
110
+ ```python
111
+ # tables.py
112
+ from sqlalchemy import Column, Integer, String, MetaData
113
+ from sqlalchemy.ext.declarative import declarative_base
114
+ from square_database_structure.square.public import global_string_schema_name
115
+
116
+ # 1. mandatory: declare Base with metadata pointing to the schema
117
+
118
+ Base = declarative_base(metadata=MetaData(schema=global_string_schema_name))
119
+
120
+
121
+ # 2.optional: define table classes by extending Base
122
+
123
+ class App(Base):
124
+ __tablename__ = 'app'
125
+
126
+
127
+ id = Column(Integer, primary_key=True)
128
+ app_name = Column(String, nullable=False)
129
+ ```
130
+
131
+ ### defining data in data.py
132
+
133
+ - you must declare a data_to_insert list to store optional data that may be inserted into the schema's tables.
134
+
135
+ ```python
136
+ from square_database_structure.square.public.tables import App
137
+
138
+ # 1. mandatory: initialize a list for optional data insertion
139
+ data_to_insert = []
140
+ # optional: append data to be inserted into the table
141
+ data_to_insert.append(App(app_name="example_app"))
142
+ ```
143
+
144
+ ### defining function or stored procedure in stored_procedures_and_functions package
145
+
146
+ - paste this logic in the `__init__.py` of this package to discover all sql files.
147
+
148
+ ```python
149
+ from pathlib import Path
150
+
151
+ directory = Path(__file__).parent
152
+ stored_procedures_and_functions = []
153
+
154
+ for file_path in directory.iterdir():
155
+ if file_path.is_file() and file_path.suffix == ".sql":
156
+ with file_path.open("r") as file:
157
+ content = file.read()
158
+ stored_procedures_and_functions.append(content)
159
+ ```
160
+
161
+ - you can keep raw sql files each containing ideally 1 stored procedure or function.
162
+ - the name of the file should ideally correspond to the function / procedure name.
163
+ - this raw sql should be compatible with postgres database.
164
+
165
+ ```sql
166
+ CREATE OR REPLACE FUNCTION add_user(
167
+ p_username VARCHAR,
168
+ p_email VARCHAR
169
+ ) RETURNS TEXT AS $$
170
+ BEGIN
171
+ -- Insert a new user into the users table
172
+ INSERT INTO users (username, email)
173
+ VALUES (p_username, p_email);
174
+
175
+ -- Return a success message
176
+ RETURN 'User added successfully!';
177
+ END;
178
+ $$ LANGUAGE plpgsql;
179
+
180
+ ```
181
+
182
+ ### centralized definitions in main.py
183
+
184
+ the main.py file is mandatory and contains a global list that maps databases to schemas and their corresponding table
185
+ definitions. this list is manually created by the user (for now).
186
+
187
+ #### example main.py:
188
+
189
+ ```python
190
+ # main.py
191
+
192
+ from square_database_structure.square.public import global_string_schema_name as schema1_name
193
+ from square_database_structure.square.public.tables import Base as Schema1Base
194
+ from square_database_structure.square.public.data import data_to_insert as schema1_data
195
+ from square_database_structure.square.public.stored_procedures_and_functions import (
196
+ stored_procedures_and_functions as schema1_stored_procedures_and_functions)
197
+ from square_database_structure.square import global_string_database_name as database1_name
198
+
199
+ # global list that maps databases and schemas
200
+
201
+ global_list_create = [
202
+ {
203
+ "database": database1_name, # mandatory: database name
204
+ "schemas": [
205
+ {
206
+ "schema": schema1_name, # mandatory: schema name
207
+ "base": Schema1Base, # mandatory: base for this schema
208
+ "data_to_insert": schema1_data, # mandatory: data to insert (even if empty)
209
+ "stored_procedures_and_functions": schema1_stored_procedures_and_functions,
210
+ # mandatory: stored procedures and functions (even if empty)
211
+ },
212
+ ],
213
+ }
214
+ ]
215
+ ```
216
+
217
+ this file centralizes the definition of each database and schema, including the associated Base and data_to_insert for
218
+ table definitions.
219
+
220
+ ### creating tables
221
+
222
+ once you have defined your databases, schemas, and tables, you can create them in your PostgreSQL database by using the
223
+ `create_database_and_tables` function.
224
+
225
+ ```python
226
+ from square_database_structure import create_database_and_tables
227
+
228
+ # define the database connection details
229
+ db_username = "your_username"
230
+ db_password = "your_password"
231
+ db_ip = "localhost"
232
+ db_port = 5432
233
+
234
+ # call the function to create the database and tables
235
+ create_database_and_tables(db_username, db_password, db_ip, db_port)
236
+ ```
237
+
238
+ ## env
239
+
240
+ - python>=3.12.0
241
+ - postgresql >= 13
242
+
243
+ > feedback is appreciated. thank you!
@@ -0,0 +1,34 @@
1
+ square_database_structure/__init__.py,sha256=gu1809vBXeu4yfyCRfgstoPHZKdljFhI2_vhnV67xis,81
2
+ square_database_structure/create_database.py,sha256=lFuK2ggVm75b7bW1RXEJjk9-ZLtVCq6hrW_Z6rDw0oI,4891
3
+ square_database_structure/main.py,sha256=u7NTFJklcCayJEmKfXfQ0DOr1xt15GIoet0dyazQnpY,4682
4
+ square_database_structure/square/__init__.py,sha256=8D9dSYmbogHBdjBa0ap6z92l_piY1l1LuG1RghkVd5g,39
5
+ square_database_structure/square/authentication/__init__.py,sha256=gCYdBEMXZ_hlmXYPXdZ9IRSJRYlyTdPwVOYkZCSu3hI,45
6
+ square_database_structure/square/authentication/data.py,sha256=1923azRFmFZMOkXPYBsbnIsU-HI2VnOfWeZRmvY9BcY,20
7
+ square_database_structure/square/authentication/enums.py,sha256=tyVRDfBth0mxX2eGc9diPH5o-1Vrcnmmgb0ntBZ-dmA,354
8
+ square_database_structure/square/authentication/stored_procedures_and_functions/__init__.py,sha256=fH4gkQGSp2rO6Dc2lbFmOqbcPX0do4z96pzBmY1UkHA,331
9
+ square_database_structure/square/authentication/tables.py,sha256=6CEBVtI1-afK1blsQzLj86jQCqxHnjlePLNRT4buLDg,6377
10
+ square_database_structure/square/email/__init__.py,sha256=3iudnzOpcR6KwtTqTzRIgE_4zHeRQe8aKGG9z7t3x7I,36
11
+ square_database_structure/square/email/data.py,sha256=1923azRFmFZMOkXPYBsbnIsU-HI2VnOfWeZRmvY9BcY,20
12
+ square_database_structure/square/email/enums.py,sha256=3tGBFq5SPYdspSlO4vuRtD_bVGVbzUtlTxXs487vawQ,194
13
+ square_database_structure/square/email/stored_procedures_and_functions/__init__.py,sha256=fH4gkQGSp2rO6Dc2lbFmOqbcPX0do4z96pzBmY1UkHA,331
14
+ square_database_structure/square/email/tables.py,sha256=Rfv-Uhm4GZbLW_9Ty_bDV4DsWmmBcbU8ksUAeITG4Ik,1443
15
+ square_database_structure/square/file_storage/__init__.py,sha256=VanngxwdOU_-4oFb6KAM1IUjWZYizsGxQO74FTX7CW0,43
16
+ square_database_structure/square/file_storage/data.py,sha256=1923azRFmFZMOkXPYBsbnIsU-HI2VnOfWeZRmvY9BcY,20
17
+ square_database_structure/square/file_storage/enums.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ square_database_structure/square/file_storage/stored_procedures_and_functions/__init__.py,sha256=fH4gkQGSp2rO6Dc2lbFmOqbcPX0do4z96pzBmY1UkHA,331
19
+ square_database_structure/square/file_storage/stored_procedures_and_functions/get_file_count.sql,sha256=fwi4FvGRLAkfDRDtDgBgWw3kA2K7_rBrzHQzZMdhil0,204
20
+ square_database_structure/square/file_storage/tables.py,sha256=IvRy_YbldgQ0ksOnu0CTq6nZ0xWamCdIIHtNM4UGPHM,1316
21
+ square_database_structure/square/greeting/__init__.py,sha256=GDFnfXKREmmxBl-YddG_EgybjJjVPguqysB_dctpWEM,39
22
+ square_database_structure/square/greeting/data.py,sha256=1923azRFmFZMOkXPYBsbnIsU-HI2VnOfWeZRmvY9BcY,20
23
+ square_database_structure/square/greeting/enums.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ square_database_structure/square/greeting/stored_procedures_and_functions/__init__.py,sha256=fH4gkQGSp2rO6Dc2lbFmOqbcPX0do4z96pzBmY1UkHA,331
25
+ square_database_structure/square/greeting/tables.py,sha256=81z6xSOMlORooZb3ckASRhhKHhyOj9cLm5nsLifW3F8,1001
26
+ square_database_structure/square/public/__init__.py,sha256=QrnJY-D0HtVeIrghwCEv1lYZoI2oGdDZV2hdPaKy3wQ,37
27
+ square_database_structure/square/public/data.py,sha256=mMzh31SyQ6blP2DbgoE-J-1lajkNQBiCaxYcgXQEhEE,279
28
+ square_database_structure/square/public/enums.py,sha256=wECAuIR4v2pzVlX_Qk6L-ITlysndwdVN4OjOWCEuGfs,143
29
+ square_database_structure/square/public/stored_procedures_and_functions/__init__.py,sha256=fH4gkQGSp2rO6Dc2lbFmOqbcPX0do4z96pzBmY1UkHA,331
30
+ square_database_structure/square/public/stored_procedures_and_functions/get_test_text.sql,sha256=-XaaAl-MbpuXYx8qFExaWR67pV_kl2dnZ4nS0UoIWO8,260
31
+ square_database_structure/square/public/tables.py,sha256=5Uanv3nqiA-Nrb_lAidSqoOmUbjJaKA7G5ppfuNRqsU,1161
32
+ square_database_structure-2.8.1.dist-info/WHEEL,sha256=GuAqCqoyQuys5_R4zkHUJFlKXw4RpRLNzo31-ui90WQ,81
33
+ square_database_structure-2.8.1.dist-info/METADATA,sha256=BYl2blwStnBiRcgd0pT48X3jw_XAn0cajHrUbTxyUvI,8415
34
+ square_database_structure-2.8.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.10.12
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any