postgresdb3 0.3.5__tar.gz → 0.4.0__tar.gz
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.
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/PKG-INFO +96 -8
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/README.md +95 -7
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3/async_db.py +177 -9
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3/sync_db.py +167 -10
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3.egg-info/PKG-INFO +96 -8
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/setup.py +1 -1
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3/__init__.py +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3.egg-info/SOURCES.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3.egg-info/dependency_links.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3.egg-info/requires.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/postgresdb3.egg-info/top_level.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/pyproject.toml +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: postgresdb3
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python uchun oddiy PostgreSQL wrapper
|
|
5
5
|
Author: Abdulbosit Alijonov
|
|
6
6
|
Project-URL: Source Code, https://github.com/AlijonovUz/PostgresDB
|
|
@@ -65,18 +65,62 @@ db.insert_many(
|
|
|
65
65
|
users = db.select("users")
|
|
66
66
|
print(users)
|
|
67
67
|
|
|
68
|
+
# Faqat kerakli ustunlarni olish
|
|
69
|
+
users = db.select("users", columns=["id", "name"])
|
|
70
|
+
print(users)
|
|
71
|
+
|
|
68
72
|
# Bitta qatorni olish
|
|
69
|
-
user = db.select("users", where=
|
|
73
|
+
user = db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
70
74
|
print(user)
|
|
71
75
|
|
|
72
76
|
# Shart bilan ma'lumot olish
|
|
73
|
-
adults = db.select("users", where=
|
|
77
|
+
adults = db.select("users", where={"age__gt": 18})
|
|
74
78
|
print(adults)
|
|
75
79
|
|
|
76
80
|
# Bir nechta shart bilan
|
|
77
|
-
specific_users = db.select(
|
|
81
|
+
specific_users = db.select(
|
|
82
|
+
"users",
|
|
83
|
+
where={
|
|
84
|
+
"age__gt": 18,
|
|
85
|
+
"name": "Ali"
|
|
86
|
+
}
|
|
87
|
+
)
|
|
78
88
|
print(specific_users)
|
|
79
89
|
|
|
90
|
+
# LIKE bilan qidirish
|
|
91
|
+
users_like = db.select("users", where={"name__like": "%Ali%"})
|
|
92
|
+
print(users_like)
|
|
93
|
+
|
|
94
|
+
# ILIKE bilan qidirish
|
|
95
|
+
users_ilike = db.select("users", where={"name__ilike": "%ali%"})
|
|
96
|
+
print(users_ilike)
|
|
97
|
+
|
|
98
|
+
# IN bilan qidirish
|
|
99
|
+
selected_users = db.select("users", where={"id__in": [1, 2, 3]})
|
|
100
|
+
print(selected_users)
|
|
101
|
+
|
|
102
|
+
# NULL bilan ishlash
|
|
103
|
+
users_with_null = db.select("users", where={"name__isnull": False})
|
|
104
|
+
print(users_with_null)
|
|
105
|
+
|
|
106
|
+
# Tartiblash va limit
|
|
107
|
+
ordered_users = db.select(
|
|
108
|
+
"users",
|
|
109
|
+
where={"age__gt": 18},
|
|
110
|
+
order_by="age DESC",
|
|
111
|
+
limit=2
|
|
112
|
+
)
|
|
113
|
+
print(ordered_users)
|
|
114
|
+
|
|
115
|
+
# Pagination
|
|
116
|
+
paged_users = db.select(
|
|
117
|
+
"users",
|
|
118
|
+
order_by="id ASC",
|
|
119
|
+
limit=2,
|
|
120
|
+
offset=2
|
|
121
|
+
)
|
|
122
|
+
print(paged_users)
|
|
123
|
+
|
|
80
124
|
# Faqat 2 ta qatorni olish
|
|
81
125
|
users = db.select("users", fetchmany=2)
|
|
82
126
|
print(users)
|
|
@@ -130,18 +174,62 @@ async def main():
|
|
|
130
174
|
users = await db.select("users")
|
|
131
175
|
print("Barcha foydalanuvchilar:", users)
|
|
132
176
|
|
|
177
|
+
# Faqat kerakli ustunlarni olish
|
|
178
|
+
users = await db.select("users", columns=["id", "name"])
|
|
179
|
+
print("Faqat id va name:", users)
|
|
180
|
+
|
|
133
181
|
# Bitta qatorni olish
|
|
134
|
-
user = await db.select("users", where=
|
|
182
|
+
user = await db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
135
183
|
print("Foydalanuvchi Ali:", user)
|
|
136
184
|
|
|
137
185
|
# Shart bilan ma'lumot olish
|
|
138
|
-
adults = await db.select("users", where=
|
|
186
|
+
adults = await db.select("users", where={"age__gt": 18})
|
|
139
187
|
print("Kattalar:", adults)
|
|
140
188
|
|
|
141
189
|
# Bir nechta shart bilan
|
|
142
|
-
specific_users = await db.select(
|
|
190
|
+
specific_users = await db.select(
|
|
191
|
+
"users",
|
|
192
|
+
where={
|
|
193
|
+
"age__gt": 18,
|
|
194
|
+
"name": "Ali"
|
|
195
|
+
}
|
|
196
|
+
)
|
|
143
197
|
print("Maxsus foydalanuvchi:", specific_users)
|
|
144
|
-
|
|
198
|
+
|
|
199
|
+
# LIKE bilan qidirish
|
|
200
|
+
users_like = await db.select("users", where={"name__like": "%Ali%"})
|
|
201
|
+
print("LIKE bo'yicha qidiruv:", users_like)
|
|
202
|
+
|
|
203
|
+
# ILIKE bilan qidirish
|
|
204
|
+
users_ilike = await db.select("users", where={"name__ilike": "%ali%"})
|
|
205
|
+
print("ILIKE bo'yicha qidiruv:", users_ilike)
|
|
206
|
+
|
|
207
|
+
# IN bilan qidirish
|
|
208
|
+
selected_users = await db.select("users", where={"id__in": [1, 2, 3]})
|
|
209
|
+
print("Tanlangan foydalanuvchilar:", selected_users)
|
|
210
|
+
|
|
211
|
+
# NULL bilan ishlash
|
|
212
|
+
users_with_not_null_name = await db.select("users", where={"name__isnull": False})
|
|
213
|
+
print("Name NULL bo'lmaganlar:", users_with_not_null_name)
|
|
214
|
+
|
|
215
|
+
# Tartiblash va limit
|
|
216
|
+
ordered_users = await db.select(
|
|
217
|
+
"users",
|
|
218
|
+
where={"age__gt": 18},
|
|
219
|
+
order_by="age DESC",
|
|
220
|
+
limit=2
|
|
221
|
+
)
|
|
222
|
+
print("Tartiblangan foydalanuvchilar:", ordered_users)
|
|
223
|
+
|
|
224
|
+
# Pagination
|
|
225
|
+
paged_users = await db.select(
|
|
226
|
+
"users",
|
|
227
|
+
order_by="id ASC",
|
|
228
|
+
limit=2,
|
|
229
|
+
offset=1
|
|
230
|
+
)
|
|
231
|
+
print("Pagination natijasi:", paged_users)
|
|
232
|
+
|
|
145
233
|
# Jadvaldagi ma'lumotni yangilash
|
|
146
234
|
await db.update("users", "age", 26, "name", "Ali")
|
|
147
235
|
|
|
@@ -43,18 +43,62 @@ db.insert_many(
|
|
|
43
43
|
users = db.select("users")
|
|
44
44
|
print(users)
|
|
45
45
|
|
|
46
|
+
# Faqat kerakli ustunlarni olish
|
|
47
|
+
users = db.select("users", columns=["id", "name"])
|
|
48
|
+
print(users)
|
|
49
|
+
|
|
46
50
|
# Bitta qatorni olish
|
|
47
|
-
user = db.select("users", where=
|
|
51
|
+
user = db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
48
52
|
print(user)
|
|
49
53
|
|
|
50
54
|
# Shart bilan ma'lumot olish
|
|
51
|
-
adults = db.select("users", where=
|
|
55
|
+
adults = db.select("users", where={"age__gt": 18})
|
|
52
56
|
print(adults)
|
|
53
57
|
|
|
54
58
|
# Bir nechta shart bilan
|
|
55
|
-
specific_users = db.select(
|
|
59
|
+
specific_users = db.select(
|
|
60
|
+
"users",
|
|
61
|
+
where={
|
|
62
|
+
"age__gt": 18,
|
|
63
|
+
"name": "Ali"
|
|
64
|
+
}
|
|
65
|
+
)
|
|
56
66
|
print(specific_users)
|
|
57
67
|
|
|
68
|
+
# LIKE bilan qidirish
|
|
69
|
+
users_like = db.select("users", where={"name__like": "%Ali%"})
|
|
70
|
+
print(users_like)
|
|
71
|
+
|
|
72
|
+
# ILIKE bilan qidirish
|
|
73
|
+
users_ilike = db.select("users", where={"name__ilike": "%ali%"})
|
|
74
|
+
print(users_ilike)
|
|
75
|
+
|
|
76
|
+
# IN bilan qidirish
|
|
77
|
+
selected_users = db.select("users", where={"id__in": [1, 2, 3]})
|
|
78
|
+
print(selected_users)
|
|
79
|
+
|
|
80
|
+
# NULL bilan ishlash
|
|
81
|
+
users_with_null = db.select("users", where={"name__isnull": False})
|
|
82
|
+
print(users_with_null)
|
|
83
|
+
|
|
84
|
+
# Tartiblash va limit
|
|
85
|
+
ordered_users = db.select(
|
|
86
|
+
"users",
|
|
87
|
+
where={"age__gt": 18},
|
|
88
|
+
order_by="age DESC",
|
|
89
|
+
limit=2
|
|
90
|
+
)
|
|
91
|
+
print(ordered_users)
|
|
92
|
+
|
|
93
|
+
# Pagination
|
|
94
|
+
paged_users = db.select(
|
|
95
|
+
"users",
|
|
96
|
+
order_by="id ASC",
|
|
97
|
+
limit=2,
|
|
98
|
+
offset=2
|
|
99
|
+
)
|
|
100
|
+
print(paged_users)
|
|
101
|
+
|
|
58
102
|
# Faqat 2 ta qatorni olish
|
|
59
103
|
users = db.select("users", fetchmany=2)
|
|
60
104
|
print(users)
|
|
@@ -108,18 +152,62 @@ async def main():
|
|
|
108
152
|
users = await db.select("users")
|
|
109
153
|
print("Barcha foydalanuvchilar:", users)
|
|
110
154
|
|
|
155
|
+
# Faqat kerakli ustunlarni olish
|
|
156
|
+
users = await db.select("users", columns=["id", "name"])
|
|
157
|
+
print("Faqat id va name:", users)
|
|
158
|
+
|
|
111
159
|
# Bitta qatorni olish
|
|
112
|
-
user = await db.select("users", where=
|
|
160
|
+
user = await db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
113
161
|
print("Foydalanuvchi Ali:", user)
|
|
114
162
|
|
|
115
163
|
# Shart bilan ma'lumot olish
|
|
116
|
-
adults = await db.select("users", where=
|
|
164
|
+
adults = await db.select("users", where={"age__gt": 18})
|
|
117
165
|
print("Kattalar:", adults)
|
|
118
166
|
|
|
119
167
|
# Bir nechta shart bilan
|
|
120
|
-
specific_users = await db.select(
|
|
168
|
+
specific_users = await db.select(
|
|
169
|
+
"users",
|
|
170
|
+
where={
|
|
171
|
+
"age__gt": 18,
|
|
172
|
+
"name": "Ali"
|
|
173
|
+
}
|
|
174
|
+
)
|
|
121
175
|
print("Maxsus foydalanuvchi:", specific_users)
|
|
122
|
-
|
|
176
|
+
|
|
177
|
+
# LIKE bilan qidirish
|
|
178
|
+
users_like = await db.select("users", where={"name__like": "%Ali%"})
|
|
179
|
+
print("LIKE bo'yicha qidiruv:", users_like)
|
|
180
|
+
|
|
181
|
+
# ILIKE bilan qidirish
|
|
182
|
+
users_ilike = await db.select("users", where={"name__ilike": "%ali%"})
|
|
183
|
+
print("ILIKE bo'yicha qidiruv:", users_ilike)
|
|
184
|
+
|
|
185
|
+
# IN bilan qidirish
|
|
186
|
+
selected_users = await db.select("users", where={"id__in": [1, 2, 3]})
|
|
187
|
+
print("Tanlangan foydalanuvchilar:", selected_users)
|
|
188
|
+
|
|
189
|
+
# NULL bilan ishlash
|
|
190
|
+
users_with_not_null_name = await db.select("users", where={"name__isnull": False})
|
|
191
|
+
print("Name NULL bo'lmaganlar:", users_with_not_null_name)
|
|
192
|
+
|
|
193
|
+
# Tartiblash va limit
|
|
194
|
+
ordered_users = await db.select(
|
|
195
|
+
"users",
|
|
196
|
+
where={"age__gt": 18},
|
|
197
|
+
order_by="age DESC",
|
|
198
|
+
limit=2
|
|
199
|
+
)
|
|
200
|
+
print("Tartiblangan foydalanuvchilar:", ordered_users)
|
|
201
|
+
|
|
202
|
+
# Pagination
|
|
203
|
+
paged_users = await db.select(
|
|
204
|
+
"users",
|
|
205
|
+
order_by="id ASC",
|
|
206
|
+
limit=2,
|
|
207
|
+
offset=1
|
|
208
|
+
)
|
|
209
|
+
print("Pagination natijasi:", paged_users)
|
|
210
|
+
|
|
123
211
|
# Jadvaldagi ma'lumotni yangilash
|
|
124
212
|
await db.update("users", "age", 26, "name", "Ali")
|
|
125
213
|
|
|
@@ -95,11 +95,171 @@ class AsyncPostgresDB:
|
|
|
95
95
|
|
|
96
96
|
await self._manager(sql, commit=True)
|
|
97
97
|
|
|
98
|
+
def _validate_identifier(self, value: str, name: str = "identifier") -> str:
|
|
99
|
+
"""
|
|
100
|
+
Jadval yoki ustun nomi uchun minimal tekshiruv.
|
|
101
|
+
Faqat harf, raqam, underscore va nuqtaga ruxsat beradi.
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(value, str) or not value.strip():
|
|
104
|
+
raise ValueError(f"{name} bo'sh bo'lmasligi kerak")
|
|
105
|
+
|
|
106
|
+
cleaned = value.replace("_", "").replace(".", "")
|
|
107
|
+
if not cleaned.isalnum():
|
|
108
|
+
raise ValueError(f"Invalid {name}: {value}")
|
|
109
|
+
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
def _normalize_columns(self, columns: str | list[str]) -> str:
|
|
113
|
+
"""
|
|
114
|
+
columns list bo'lsa stringga aylantiradi.
|
|
115
|
+
list bo'lsa har bir column validate qilinadi.
|
|
116
|
+
str bo'lsa SQL expression bo'lishi mumkinligi uchun o'z holicha qaytariladi.
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(columns, list):
|
|
119
|
+
if not columns:
|
|
120
|
+
raise ValueError("columns bo'sh list bo'lmasligi kerak")
|
|
121
|
+
|
|
122
|
+
validated_columns = [self._validate_identifier(col, "column") for col in columns]
|
|
123
|
+
return ", ".join(validated_columns)
|
|
124
|
+
|
|
125
|
+
if not isinstance(columns, str) or not columns.strip():
|
|
126
|
+
raise ValueError("columns noto'g'ri bo'lishi mumkin emas")
|
|
127
|
+
|
|
128
|
+
return columns
|
|
129
|
+
|
|
130
|
+
def _build_where(self, where: Any, start_index: int = 1) -> tuple[str, list]:
|
|
131
|
+
"""
|
|
132
|
+
Qo'llab-quvvatlanadi:
|
|
133
|
+
|
|
134
|
+
1) tuple:
|
|
135
|
+
("age > $1", [18])
|
|
136
|
+
|
|
137
|
+
2) dict:
|
|
138
|
+
{
|
|
139
|
+
"age__gt": 18,
|
|
140
|
+
"name__ilike": "%ali%",
|
|
141
|
+
"status": "active",
|
|
142
|
+
"id__in": [1, 2, 3],
|
|
143
|
+
"deleted_at__isnull": True
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
3) list[tuple]:
|
|
147
|
+
[
|
|
148
|
+
("age", ">", 18),
|
|
149
|
+
("name", "ILIKE", "%ali%"),
|
|
150
|
+
("status", "=", "active")
|
|
151
|
+
]
|
|
152
|
+
"""
|
|
153
|
+
if where is None:
|
|
154
|
+
return "", []
|
|
155
|
+
|
|
156
|
+
if isinstance(where, tuple) and len(where) == 2:
|
|
157
|
+
condition, values = where
|
|
158
|
+
return condition, list(values)
|
|
159
|
+
|
|
160
|
+
if isinstance(where, dict):
|
|
161
|
+
clauses = []
|
|
162
|
+
params = []
|
|
163
|
+
index = start_index
|
|
164
|
+
|
|
165
|
+
operator_map = {
|
|
166
|
+
"eq": "=",
|
|
167
|
+
"ne": "!=",
|
|
168
|
+
"gt": ">",
|
|
169
|
+
"gte": ">=",
|
|
170
|
+
"lt": "<",
|
|
171
|
+
"lte": "<=",
|
|
172
|
+
"like": "LIKE",
|
|
173
|
+
"ilike": "ILIKE",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for key, value in where.items():
|
|
177
|
+
if "__" in key:
|
|
178
|
+
field, op = key.rsplit("__", 1)
|
|
179
|
+
else:
|
|
180
|
+
field, op = key, "eq"
|
|
181
|
+
|
|
182
|
+
self._validate_identifier(field, "where field")
|
|
183
|
+
|
|
184
|
+
if op in operator_map:
|
|
185
|
+
clauses.append(f"{field} {operator_map[op]} ${index}")
|
|
186
|
+
params.append(value)
|
|
187
|
+
index += 1
|
|
188
|
+
|
|
189
|
+
elif op == "in":
|
|
190
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
191
|
+
raise ValueError(f"{field}__in uchun bo'sh bo'lmagan list/tuple kerak")
|
|
192
|
+
|
|
193
|
+
placeholders = ", ".join(f"${i}" for i in range(index, index + len(value)))
|
|
194
|
+
clauses.append(f"{field} IN ({placeholders})")
|
|
195
|
+
params.extend(value)
|
|
196
|
+
index += len(value)
|
|
197
|
+
|
|
198
|
+
elif op == "not_in":
|
|
199
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
200
|
+
raise ValueError(f"{field}__not_in uchun bo'sh bo'lmagan list/tuple kerak")
|
|
201
|
+
|
|
202
|
+
placeholders = ", ".join(f"${i}" for i in range(index, index + len(value)))
|
|
203
|
+
clauses.append(f"{field} NOT IN ({placeholders})")
|
|
204
|
+
params.extend(value)
|
|
205
|
+
index += len(value)
|
|
206
|
+
|
|
207
|
+
elif op == "isnull":
|
|
208
|
+
if value:
|
|
209
|
+
clauses.append(f"{field} IS NULL")
|
|
210
|
+
else:
|
|
211
|
+
clauses.append(f"{field} IS NOT NULL")
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(f"Noma'lum operator: {op}")
|
|
215
|
+
|
|
216
|
+
return " AND ".join(clauses), params
|
|
217
|
+
|
|
218
|
+
if isinstance(where, list):
|
|
219
|
+
clauses = []
|
|
220
|
+
params = []
|
|
221
|
+
index = start_index
|
|
222
|
+
|
|
223
|
+
for item in where:
|
|
224
|
+
if len(item) != 3:
|
|
225
|
+
raise ValueError("List formatidagi where elementlari (field, operator, value) bo'lishi kerak")
|
|
226
|
+
|
|
227
|
+
field, operator, value = item
|
|
228
|
+
self._validate_identifier(field, "where field")
|
|
229
|
+
op = operator.upper()
|
|
230
|
+
|
|
231
|
+
if op in {"=", "!=", ">", ">=", "<", "<=", "LIKE", "ILIKE"}:
|
|
232
|
+
clauses.append(f"{field} {op} ${index}")
|
|
233
|
+
params.append(value)
|
|
234
|
+
index += 1
|
|
235
|
+
|
|
236
|
+
elif op in {"IN", "NOT IN"}:
|
|
237
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
238
|
+
raise ValueError(f"{field} {op} uchun bo'sh bo'lmagan list/tuple kerak")
|
|
239
|
+
|
|
240
|
+
placeholders = ", ".join(f"${i}" for i in range(index, index + len(value)))
|
|
241
|
+
clauses.append(f"{field} {op} ({placeholders})")
|
|
242
|
+
params.extend(value)
|
|
243
|
+
index += len(value)
|
|
244
|
+
|
|
245
|
+
elif op == "IS NULL":
|
|
246
|
+
clauses.append(f"{field} IS NULL")
|
|
247
|
+
|
|
248
|
+
elif op == "IS NOT NULL":
|
|
249
|
+
clauses.append(f"{field} IS NOT NULL")
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
raise ValueError(f"Noma'lum operator: {operator}")
|
|
253
|
+
|
|
254
|
+
return " AND ".join(clauses), params
|
|
255
|
+
|
|
256
|
+
raise TypeError("where tuple, dict yoki list bo'lishi kerak")
|
|
257
|
+
|
|
98
258
|
async def select(
|
|
99
259
|
self,
|
|
100
260
|
table: str,
|
|
101
|
-
columns: str = "*",
|
|
102
|
-
where:
|
|
261
|
+
columns: str | list[str] = "*",
|
|
262
|
+
where: tuple | dict | list | None = None,
|
|
103
263
|
join: Optional[List[tuple]] = None,
|
|
104
264
|
group_by: Optional[str] = None,
|
|
105
265
|
order_by: Optional[str] = None,
|
|
@@ -112,8 +272,11 @@ class AsyncPostgresDB:
|
|
|
112
272
|
|
|
113
273
|
Args:
|
|
114
274
|
table (str): Asosiy jadval nomi.
|
|
115
|
-
columns (str): Tanlanadigan ustunlar
|
|
116
|
-
where
|
|
275
|
+
columns (str | list[str]): Tanlanadigan ustunlar.
|
|
276
|
+
where:
|
|
277
|
+
tuple — ("age > $1", [18])
|
|
278
|
+
dict — {"age__gt": 18, "name__ilike": "%ali%"}
|
|
279
|
+
list — [("age", ">", 18), ("name", "ILIKE", "%ali%")]
|
|
117
280
|
join (Optional[List[tuple]]): [("INNER JOIN", "orders", "users.id = orders.user_id")]
|
|
118
281
|
group_by (Optional[str]): GROUP BY ustuni.
|
|
119
282
|
order_by (Optional[str]): ORDER BY qoidasi.
|
|
@@ -124,17 +287,22 @@ class AsyncPostgresDB:
|
|
|
124
287
|
Returns:
|
|
125
288
|
asyncpg.Record yoki list[asyncpg.Record]
|
|
126
289
|
"""
|
|
290
|
+
table = self._validate_identifier(table, "table")
|
|
291
|
+
columns = self._normalize_columns(columns)
|
|
292
|
+
|
|
127
293
|
sql = f"SELECT {columns} FROM {table}"
|
|
128
294
|
params: List[Any] = []
|
|
129
295
|
|
|
130
296
|
if join:
|
|
131
297
|
for join_type, join_table, on_condition in join:
|
|
298
|
+
join_table = self._validate_identifier(join_table, "join table")
|
|
132
299
|
sql += f" {join_type} {join_table} ON {on_condition}"
|
|
133
300
|
|
|
134
301
|
if where:
|
|
135
|
-
condition, values = where
|
|
136
|
-
|
|
137
|
-
|
|
302
|
+
condition, values = self._build_where(where, start_index=1)
|
|
303
|
+
if condition:
|
|
304
|
+
sql += f" WHERE {condition}"
|
|
305
|
+
params.extend(values)
|
|
138
306
|
|
|
139
307
|
if group_by:
|
|
140
308
|
sql += f" GROUP BY {group_by}"
|
|
@@ -143,11 +311,11 @@ class AsyncPostgresDB:
|
|
|
143
311
|
sql += f" ORDER BY {order_by}"
|
|
144
312
|
|
|
145
313
|
if limit is not None:
|
|
146
|
-
sql += f" LIMIT
|
|
314
|
+
sql += f" LIMIT ${len(params) + 1}"
|
|
147
315
|
params.append(limit)
|
|
148
316
|
|
|
149
317
|
if offset is not None:
|
|
150
|
-
sql += " OFFSET
|
|
318
|
+
sql += f" OFFSET ${len(params) + 1}"
|
|
151
319
|
params.append(offset)
|
|
152
320
|
|
|
153
321
|
return await self._manager(sql, *params, fetchone=fetchone, fetchall=not fetchone)
|
|
@@ -204,11 +204,158 @@ class PostgresDB:
|
|
|
204
204
|
sql += " CASCADE"
|
|
205
205
|
self._manager(sql, commit=True)
|
|
206
206
|
|
|
207
|
+
def _build_where(self, where: Any) -> tuple[str, list]:
|
|
208
|
+
"""
|
|
209
|
+
Qo'llab-quvvatlanadi:
|
|
210
|
+
|
|
211
|
+
1) tuple:
|
|
212
|
+
("age > %s", [18])
|
|
213
|
+
|
|
214
|
+
2) dict:
|
|
215
|
+
{
|
|
216
|
+
"age__gt": 18,
|
|
217
|
+
"name__ilike": "%ali%",
|
|
218
|
+
"status": "active",
|
|
219
|
+
"id__in": [1, 2, 3],
|
|
220
|
+
"deleted_at__isnull": True
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
3) list[tuple]:
|
|
224
|
+
[
|
|
225
|
+
("age", ">", 18),
|
|
226
|
+
("name", "ILIKE", "%ali%"),
|
|
227
|
+
("status", "=", "active")
|
|
228
|
+
]
|
|
229
|
+
"""
|
|
230
|
+
if where is None:
|
|
231
|
+
return "", []
|
|
232
|
+
|
|
233
|
+
if isinstance(where, tuple) and len(where) == 2:
|
|
234
|
+
condition, values = where
|
|
235
|
+
return condition, list(values)
|
|
236
|
+
|
|
237
|
+
if isinstance(where, dict):
|
|
238
|
+
clauses = []
|
|
239
|
+
params = []
|
|
240
|
+
|
|
241
|
+
operator_map = {
|
|
242
|
+
"eq": "=",
|
|
243
|
+
"ne": "!=",
|
|
244
|
+
"gt": ">",
|
|
245
|
+
"gte": ">=",
|
|
246
|
+
"lt": "<",
|
|
247
|
+
"lte": "<=",
|
|
248
|
+
"like": "LIKE",
|
|
249
|
+
"ilike": "ILIKE",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for key, value in where.items():
|
|
253
|
+
if "__" in key:
|
|
254
|
+
field, op = key.rsplit("__", 1)
|
|
255
|
+
else:
|
|
256
|
+
field, op = key, "eq"
|
|
257
|
+
|
|
258
|
+
if op in operator_map:
|
|
259
|
+
clauses.append(f"{field} {operator_map[op]} %s")
|
|
260
|
+
params.append(value)
|
|
261
|
+
|
|
262
|
+
elif op == "in":
|
|
263
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
264
|
+
raise ValueError(f"{field}__in uchun bo'sh bo'lmagan list/tuple kerak")
|
|
265
|
+
placeholders = ", ".join(["%s"] * len(value))
|
|
266
|
+
clauses.append(f"{field} IN ({placeholders})")
|
|
267
|
+
params.extend(value)
|
|
268
|
+
|
|
269
|
+
elif op == "not_in":
|
|
270
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
271
|
+
raise ValueError(f"{field}__not_in uchun bo'sh bo'lmagan list/tuple kerak")
|
|
272
|
+
placeholders = ", ".join(["%s"] * len(value))
|
|
273
|
+
clauses.append(f"{field} NOT IN ({placeholders})")
|
|
274
|
+
params.extend(value)
|
|
275
|
+
|
|
276
|
+
elif op == "isnull":
|
|
277
|
+
if value:
|
|
278
|
+
clauses.append(f"{field} IS NULL")
|
|
279
|
+
else:
|
|
280
|
+
clauses.append(f"{field} IS NOT NULL")
|
|
281
|
+
|
|
282
|
+
else:
|
|
283
|
+
raise ValueError(f"Noma'lum operator: {op}")
|
|
284
|
+
|
|
285
|
+
return " AND ".join(clauses), params
|
|
286
|
+
|
|
287
|
+
if isinstance(where, list):
|
|
288
|
+
clauses = []
|
|
289
|
+
params = []
|
|
290
|
+
|
|
291
|
+
for item in where:
|
|
292
|
+
if len(item) != 3:
|
|
293
|
+
raise ValueError("List formatidagi where elementlari (field, operator, value) bo'lishi kerak")
|
|
294
|
+
|
|
295
|
+
field, operator, value = item
|
|
296
|
+
op = operator.upper()
|
|
297
|
+
|
|
298
|
+
if op in {"=", "!=", ">", ">=", "<", "<=", "LIKE", "ILIKE"}:
|
|
299
|
+
clauses.append(f"{field} {op} %s")
|
|
300
|
+
params.append(value)
|
|
301
|
+
|
|
302
|
+
elif op in {"IN", "NOT IN"}:
|
|
303
|
+
if not isinstance(value, (list, tuple)) or not value:
|
|
304
|
+
raise ValueError(f"{field} {op} uchun bo'sh bo'lmagan list/tuple kerak")
|
|
305
|
+
placeholders = ", ".join(["%s"] * len(value))
|
|
306
|
+
clauses.append(f"{field} {op} ({placeholders})")
|
|
307
|
+
params.extend(value)
|
|
308
|
+
|
|
309
|
+
elif op == "IS NULL":
|
|
310
|
+
clauses.append(f"{field} IS NULL")
|
|
311
|
+
|
|
312
|
+
elif op == "IS NOT NULL":
|
|
313
|
+
clauses.append(f"{field} IS NOT NULL")
|
|
314
|
+
|
|
315
|
+
else:
|
|
316
|
+
raise ValueError(f"Noma'lum operator: {operator}")
|
|
317
|
+
|
|
318
|
+
return " AND ".join(clauses), params
|
|
319
|
+
|
|
320
|
+
raise TypeError("where tuple, dict yoki list bo'lishi kerak")
|
|
321
|
+
|
|
322
|
+
def _validate_identifier(self, value: str, name: str = "identifier") -> str:
|
|
323
|
+
"""
|
|
324
|
+
Jadval yoki ustun nomi uchun minimal tekshiruv.
|
|
325
|
+
Faqat harf, raqam, underscore va nuqtaga ruxsat beradi.
|
|
326
|
+
"""
|
|
327
|
+
if not isinstance(value, str) or not value.strip():
|
|
328
|
+
raise ValueError(f"{name} bo'sh bo'lmasligi kerak")
|
|
329
|
+
|
|
330
|
+
cleaned = value.replace("_", "").replace(".", "")
|
|
331
|
+
if not cleaned.isalnum():
|
|
332
|
+
raise ValueError(f"Invalid {name}: {value}")
|
|
333
|
+
|
|
334
|
+
return value
|
|
335
|
+
|
|
336
|
+
def _normalize_columns(self, columns: str | list[str]) -> str:
|
|
337
|
+
"""
|
|
338
|
+
columns list bo'lsa stringga aylantiradi.
|
|
339
|
+
list bo'lsa har bir column validate qilinadi.
|
|
340
|
+
str bo'lsa SQL expression bo'lishi mumkinligi uchun o'z holicha qaytariladi.
|
|
341
|
+
"""
|
|
342
|
+
if isinstance(columns, list):
|
|
343
|
+
if not columns:
|
|
344
|
+
raise ValueError("columns bo'sh list bo'lmasligi kerak")
|
|
345
|
+
|
|
346
|
+
validated_columns = [self._validate_identifier(col, "column") for col in columns]
|
|
347
|
+
return ", ".join(validated_columns)
|
|
348
|
+
|
|
349
|
+
if not isinstance(columns, str) or not columns.strip():
|
|
350
|
+
raise ValueError("columns noto'g'ri bo'lishi mumkin emas")
|
|
351
|
+
|
|
352
|
+
return columns
|
|
353
|
+
|
|
207
354
|
def select(
|
|
208
355
|
self,
|
|
209
356
|
table: str,
|
|
210
|
-
columns: str = "*",
|
|
211
|
-
where: tuple | None = None,
|
|
357
|
+
columns: str | list[str] = "*",
|
|
358
|
+
where: tuple | dict | list | None = None,
|
|
212
359
|
join: list[tuple] | None = None,
|
|
213
360
|
group_by: str | None = None,
|
|
214
361
|
order_by: str | None = None,
|
|
@@ -219,8 +366,13 @@ class PostgresDB:
|
|
|
219
366
|
) -> Any:
|
|
220
367
|
"""
|
|
221
368
|
table: str — asosiy jadval
|
|
222
|
-
columns:
|
|
223
|
-
|
|
369
|
+
columns:
|
|
370
|
+
str — "id, name"
|
|
371
|
+
list[str] — ["id", "name"]
|
|
372
|
+
where:
|
|
373
|
+
tuple — ("age > %s", [18])
|
|
374
|
+
dict — {"age__gt": 18, "name__ilike": "%ali%"}
|
|
375
|
+
list — [("age", ">", 18), ("name", "ILIKE", "%ali%")]
|
|
224
376
|
join: list | None — [("INNER JOIN", "orders", "users.id = orders.user_id")]
|
|
225
377
|
group_by: str | None — "age"
|
|
226
378
|
order_by: str | None — "age DESC"
|
|
@@ -230,17 +382,22 @@ class PostgresDB:
|
|
|
230
382
|
fetchmany: int | None
|
|
231
383
|
"""
|
|
232
384
|
|
|
385
|
+
table = self._validate_identifier(table, "table")
|
|
386
|
+
columns = self._normalize_columns(columns)
|
|
387
|
+
|
|
233
388
|
sql = f"SELECT {columns} FROM {table}"
|
|
234
389
|
params = []
|
|
235
390
|
|
|
236
391
|
if join:
|
|
237
392
|
for join_type, join_table, on_condition in join:
|
|
393
|
+
join_table = self._validate_identifier(join_table, "join table")
|
|
238
394
|
sql += f" {join_type} {join_table} ON {on_condition}"
|
|
239
395
|
|
|
240
396
|
if where:
|
|
241
|
-
condition, values = where
|
|
242
|
-
|
|
243
|
-
|
|
397
|
+
condition, values = self._build_where(where)
|
|
398
|
+
if condition:
|
|
399
|
+
sql += f" WHERE {condition}"
|
|
400
|
+
params.extend(values)
|
|
244
401
|
|
|
245
402
|
if group_by:
|
|
246
403
|
sql += f" GROUP BY {group_by}"
|
|
@@ -249,7 +406,7 @@ class PostgresDB:
|
|
|
249
406
|
sql += f" ORDER BY {order_by}"
|
|
250
407
|
|
|
251
408
|
if limit is not None:
|
|
252
|
-
sql +=
|
|
409
|
+
sql += " LIMIT %s"
|
|
253
410
|
params.append(limit)
|
|
254
411
|
|
|
255
412
|
if offset is not None:
|
|
@@ -337,7 +494,7 @@ class PostgresDB:
|
|
|
337
494
|
SELECT table_name
|
|
338
495
|
FROM information_schema.tables
|
|
339
496
|
WHERE table_schema = %s
|
|
340
|
-
ORDER BY table_name;
|
|
497
|
+
ORDER BY table_name; \
|
|
341
498
|
"""
|
|
342
499
|
return self._manager(sql, (schema,), fetchall=True)
|
|
343
500
|
|
|
@@ -353,7 +510,7 @@ class PostgresDB:
|
|
|
353
510
|
FROM information_schema.columns
|
|
354
511
|
WHERE table_schema = %s
|
|
355
512
|
AND table_name = %s
|
|
356
|
-
ORDER BY ordinal_position;
|
|
513
|
+
ORDER BY ordinal_position; \
|
|
357
514
|
"""
|
|
358
515
|
return self._manager(sql, (schema, table), fetchall=True)
|
|
359
516
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: postgresdb3
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python uchun oddiy PostgreSQL wrapper
|
|
5
5
|
Author: Abdulbosit Alijonov
|
|
6
6
|
Project-URL: Source Code, https://github.com/AlijonovUz/PostgresDB
|
|
@@ -65,18 +65,62 @@ db.insert_many(
|
|
|
65
65
|
users = db.select("users")
|
|
66
66
|
print(users)
|
|
67
67
|
|
|
68
|
+
# Faqat kerakli ustunlarni olish
|
|
69
|
+
users = db.select("users", columns=["id", "name"])
|
|
70
|
+
print(users)
|
|
71
|
+
|
|
68
72
|
# Bitta qatorni olish
|
|
69
|
-
user = db.select("users", where=
|
|
73
|
+
user = db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
70
74
|
print(user)
|
|
71
75
|
|
|
72
76
|
# Shart bilan ma'lumot olish
|
|
73
|
-
adults = db.select("users", where=
|
|
77
|
+
adults = db.select("users", where={"age__gt": 18})
|
|
74
78
|
print(adults)
|
|
75
79
|
|
|
76
80
|
# Bir nechta shart bilan
|
|
77
|
-
specific_users = db.select(
|
|
81
|
+
specific_users = db.select(
|
|
82
|
+
"users",
|
|
83
|
+
where={
|
|
84
|
+
"age__gt": 18,
|
|
85
|
+
"name": "Ali"
|
|
86
|
+
}
|
|
87
|
+
)
|
|
78
88
|
print(specific_users)
|
|
79
89
|
|
|
90
|
+
# LIKE bilan qidirish
|
|
91
|
+
users_like = db.select("users", where={"name__like": "%Ali%"})
|
|
92
|
+
print(users_like)
|
|
93
|
+
|
|
94
|
+
# ILIKE bilan qidirish
|
|
95
|
+
users_ilike = db.select("users", where={"name__ilike": "%ali%"})
|
|
96
|
+
print(users_ilike)
|
|
97
|
+
|
|
98
|
+
# IN bilan qidirish
|
|
99
|
+
selected_users = db.select("users", where={"id__in": [1, 2, 3]})
|
|
100
|
+
print(selected_users)
|
|
101
|
+
|
|
102
|
+
# NULL bilan ishlash
|
|
103
|
+
users_with_null = db.select("users", where={"name__isnull": False})
|
|
104
|
+
print(users_with_null)
|
|
105
|
+
|
|
106
|
+
# Tartiblash va limit
|
|
107
|
+
ordered_users = db.select(
|
|
108
|
+
"users",
|
|
109
|
+
where={"age__gt": 18},
|
|
110
|
+
order_by="age DESC",
|
|
111
|
+
limit=2
|
|
112
|
+
)
|
|
113
|
+
print(ordered_users)
|
|
114
|
+
|
|
115
|
+
# Pagination
|
|
116
|
+
paged_users = db.select(
|
|
117
|
+
"users",
|
|
118
|
+
order_by="id ASC",
|
|
119
|
+
limit=2,
|
|
120
|
+
offset=2
|
|
121
|
+
)
|
|
122
|
+
print(paged_users)
|
|
123
|
+
|
|
80
124
|
# Faqat 2 ta qatorni olish
|
|
81
125
|
users = db.select("users", fetchmany=2)
|
|
82
126
|
print(users)
|
|
@@ -130,18 +174,62 @@ async def main():
|
|
|
130
174
|
users = await db.select("users")
|
|
131
175
|
print("Barcha foydalanuvchilar:", users)
|
|
132
176
|
|
|
177
|
+
# Faqat kerakli ustunlarni olish
|
|
178
|
+
users = await db.select("users", columns=["id", "name"])
|
|
179
|
+
print("Faqat id va name:", users)
|
|
180
|
+
|
|
133
181
|
# Bitta qatorni olish
|
|
134
|
-
user = await db.select("users", where=
|
|
182
|
+
user = await db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
135
183
|
print("Foydalanuvchi Ali:", user)
|
|
136
184
|
|
|
137
185
|
# Shart bilan ma'lumot olish
|
|
138
|
-
adults = await db.select("users", where=
|
|
186
|
+
adults = await db.select("users", where={"age__gt": 18})
|
|
139
187
|
print("Kattalar:", adults)
|
|
140
188
|
|
|
141
189
|
# Bir nechta shart bilan
|
|
142
|
-
specific_users = await db.select(
|
|
190
|
+
specific_users = await db.select(
|
|
191
|
+
"users",
|
|
192
|
+
where={
|
|
193
|
+
"age__gt": 18,
|
|
194
|
+
"name": "Ali"
|
|
195
|
+
}
|
|
196
|
+
)
|
|
143
197
|
print("Maxsus foydalanuvchi:", specific_users)
|
|
144
|
-
|
|
198
|
+
|
|
199
|
+
# LIKE bilan qidirish
|
|
200
|
+
users_like = await db.select("users", where={"name__like": "%Ali%"})
|
|
201
|
+
print("LIKE bo'yicha qidiruv:", users_like)
|
|
202
|
+
|
|
203
|
+
# ILIKE bilan qidirish
|
|
204
|
+
users_ilike = await db.select("users", where={"name__ilike": "%ali%"})
|
|
205
|
+
print("ILIKE bo'yicha qidiruv:", users_ilike)
|
|
206
|
+
|
|
207
|
+
# IN bilan qidirish
|
|
208
|
+
selected_users = await db.select("users", where={"id__in": [1, 2, 3]})
|
|
209
|
+
print("Tanlangan foydalanuvchilar:", selected_users)
|
|
210
|
+
|
|
211
|
+
# NULL bilan ishlash
|
|
212
|
+
users_with_not_null_name = await db.select("users", where={"name__isnull": False})
|
|
213
|
+
print("Name NULL bo'lmaganlar:", users_with_not_null_name)
|
|
214
|
+
|
|
215
|
+
# Tartiblash va limit
|
|
216
|
+
ordered_users = await db.select(
|
|
217
|
+
"users",
|
|
218
|
+
where={"age__gt": 18},
|
|
219
|
+
order_by="age DESC",
|
|
220
|
+
limit=2
|
|
221
|
+
)
|
|
222
|
+
print("Tartiblangan foydalanuvchilar:", ordered_users)
|
|
223
|
+
|
|
224
|
+
# Pagination
|
|
225
|
+
paged_users = await db.select(
|
|
226
|
+
"users",
|
|
227
|
+
order_by="id ASC",
|
|
228
|
+
limit=2,
|
|
229
|
+
offset=1
|
|
230
|
+
)
|
|
231
|
+
print("Pagination natijasi:", paged_users)
|
|
232
|
+
|
|
145
233
|
# Jadvaldagi ma'lumotni yangilash
|
|
146
234
|
await db.update("users", "age", 26, "name", "Ali")
|
|
147
235
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|