postgresdb3 0.3.5__tar.gz → 0.5.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.5.0/PKG-INFO +255 -0
- postgresdb3-0.5.0/README.md +233 -0
- postgresdb3-0.5.0/postgresdb3/__init__.py +1 -0
- {postgresdb3-0.3.5/postgresdb3 → postgresdb3-0.5.0/postgresdb3/core}/async_db.py +177 -9
- {postgresdb3-0.3.5/postgresdb3 → postgresdb3-0.5.0/postgresdb3/core}/sync_db.py +167 -10
- postgresdb3-0.5.0/postgresdb3/orm/__init__.py +1 -0
- postgresdb3-0.5.0/postgresdb3/orm/base.py +91 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/__init__.py +6 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/base.py +37 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/datetime.py +17 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/misc.py +28 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/numeric.py +32 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/relations.py +16 -0
- postgresdb3-0.5.0/postgresdb3/orm/fields/text.py +15 -0
- postgresdb3-0.5.0/postgresdb3/orm/meta.py +41 -0
- postgresdb3-0.5.0/postgresdb3/orm/models.py +276 -0
- postgresdb3-0.5.0/postgresdb3/orm/query.py +119 -0
- postgresdb3-0.5.0/postgresdb3.egg-info/PKG-INFO +255 -0
- postgresdb3-0.5.0/postgresdb3.egg-info/SOURCES.txt +24 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/setup.py +1 -1
- postgresdb3-0.3.5/PKG-INFO +0 -209
- postgresdb3-0.3.5/README.md +0 -187
- postgresdb3-0.3.5/postgresdb3.egg-info/PKG-INFO +0 -209
- postgresdb3-0.3.5/postgresdb3.egg-info/SOURCES.txt +0 -11
- {postgresdb3-0.3.5/postgresdb3 → postgresdb3-0.5.0/postgresdb3/core}/__init__.py +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/postgresdb3.egg-info/dependency_links.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/postgresdb3.egg-info/requires.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/postgresdb3.egg-info/top_level.txt +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/pyproject.toml +0 -0
- {postgresdb3-0.3.5 → postgresdb3-0.5.0}/setup.cfg +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: postgresdb3
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Python uchun oddiy PostgreSQL wrapper
|
|
5
|
+
Author: Abdulbosit Alijonov
|
|
6
|
+
Project-URL: Source Code, https://github.com/AlijonovUz/PostgresDB
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: psycopg2>=2.9
|
|
13
|
+
Requires-Dist: asyncpg>=0.31.0
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: project-url
|
|
19
|
+
Dynamic: requires-dist
|
|
20
|
+
Dynamic: requires-python
|
|
21
|
+
Dynamic: summary
|
|
22
|
+
|
|
23
|
+
# PostgresDB3
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+

|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**PostgresDB3** — PostgreSQL bilan ishlash uchun oddiy, qulay va yengil Python wrapper.
|
|
30
|
+
Kutubxona sync va async ishlashni qo‘llab-quvvatlaydi hamda sodda ORM imkoniyatlarini ham taqdim etadi.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# O‘rnatish
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install postgresdb3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Asosiy imkoniyatlar
|
|
43
|
+
|
|
44
|
+
* PostgreSQL bilan **sync va async** ishlash
|
|
45
|
+
* CRUD amallari
|
|
46
|
+
* `where`, `join`, `group_by`, `order_by`, `limit`, `offset`
|
|
47
|
+
* `like`, `ilike`, `in`, `isnull`, `gt`, `lt` kabi filterlar
|
|
48
|
+
* Raw SQL bajarish imkoniyati
|
|
49
|
+
* Sodda ORM tizimi
|
|
50
|
+
* Async ORM qo‘llab-quvvatlash
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
# Sync foydalanish
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from postgresdb3 import PostgresDB
|
|
58
|
+
|
|
59
|
+
db = PostgresDB(
|
|
60
|
+
database="mydb",
|
|
61
|
+
user="postgres",
|
|
62
|
+
password="mypassword",
|
|
63
|
+
host="localhost",
|
|
64
|
+
port=5432
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Jadval yaratish
|
|
68
|
+
db.create("users", "id SERIAL PRIMARY KEY, name VARCHAR(100), age INT")
|
|
69
|
+
|
|
70
|
+
# Bitta qator qo‘shish
|
|
71
|
+
db.insert("users", "name, age", ("Ali", 25))
|
|
72
|
+
|
|
73
|
+
# Bir nechta qator qo‘shish
|
|
74
|
+
db.insert_many(
|
|
75
|
+
"users",
|
|
76
|
+
"name, age",
|
|
77
|
+
[("Vali", 30), ("Gulnoza", 22), ("Hasan", 28)]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Barcha ma'lumotlarni olish
|
|
81
|
+
users = db.select("users")
|
|
82
|
+
print(users)
|
|
83
|
+
|
|
84
|
+
# Faqat kerakli ustunlarni olish
|
|
85
|
+
users = db.select("users", columns=["id", "name"])
|
|
86
|
+
print(users)
|
|
87
|
+
|
|
88
|
+
# Bitta qatorni olish
|
|
89
|
+
user = db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
90
|
+
print(user)
|
|
91
|
+
|
|
92
|
+
# Shart bilan qidirish
|
|
93
|
+
adults = db.select("users", where={"age__gt": 18})
|
|
94
|
+
print(adults)
|
|
95
|
+
|
|
96
|
+
# LIKE qidiruv
|
|
97
|
+
users_like = db.select("users", where={"name__like": "%Ali%"})
|
|
98
|
+
print(users_like)
|
|
99
|
+
|
|
100
|
+
# ILIKE qidiruv
|
|
101
|
+
users_ilike = db.select("users", where={"name__ilike": "%ali%"})
|
|
102
|
+
print(users_ilike)
|
|
103
|
+
|
|
104
|
+
# IN operatori
|
|
105
|
+
selected_users = db.select("users", where={"id__in": [1, 2, 3]})
|
|
106
|
+
print(selected_users)
|
|
107
|
+
|
|
108
|
+
# NULL tekshirish
|
|
109
|
+
users_with_name = db.select("users", where={"name__isnull": False})
|
|
110
|
+
print(users_with_name)
|
|
111
|
+
|
|
112
|
+
# Tartiblash va limit
|
|
113
|
+
ordered_users = db.select(
|
|
114
|
+
"users",
|
|
115
|
+
where={"age__gt": 18},
|
|
116
|
+
order_by="age DESC",
|
|
117
|
+
limit=2
|
|
118
|
+
)
|
|
119
|
+
print(ordered_users)
|
|
120
|
+
|
|
121
|
+
# Pagination
|
|
122
|
+
paged_users = db.select(
|
|
123
|
+
"users",
|
|
124
|
+
order_by="id ASC",
|
|
125
|
+
limit=2,
|
|
126
|
+
offset=2
|
|
127
|
+
)
|
|
128
|
+
print(paged_users)
|
|
129
|
+
|
|
130
|
+
# Ma'lumotni yangilash
|
|
131
|
+
db.update("users", "age", 26, "name", "Ali")
|
|
132
|
+
|
|
133
|
+
# Ma'lumotni o‘chirish
|
|
134
|
+
db.delete("users", "name", "Ali")
|
|
135
|
+
|
|
136
|
+
# Jadvalni o‘chirish
|
|
137
|
+
db.drop("users", cascade=False)
|
|
138
|
+
|
|
139
|
+
# Raw SQL bajarish
|
|
140
|
+
result = db.raw("SELECT name, age FROM users WHERE age > %s", [25], fetchall=True)
|
|
141
|
+
print(result)
|
|
142
|
+
|
|
143
|
+
# Connectionni yopish
|
|
144
|
+
db.close()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# Async foydalanish
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import asyncio
|
|
153
|
+
from postgresdb3 import AsyncPostgresDB
|
|
154
|
+
|
|
155
|
+
async def main():
|
|
156
|
+
db = AsyncPostgresDB(
|
|
157
|
+
database="mydb",
|
|
158
|
+
user="postgres",
|
|
159
|
+
password="mypassword",
|
|
160
|
+
host="localhost",
|
|
161
|
+
port=5432
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
await db.create("users", "id SERIAL PRIMARY KEY, name VARCHAR(100), age INT")
|
|
165
|
+
|
|
166
|
+
await db.insert("users", "name, age", ["Ali", 25])
|
|
167
|
+
|
|
168
|
+
await db.insert_many(
|
|
169
|
+
"users",
|
|
170
|
+
"name, age",
|
|
171
|
+
[["Vali", 30], ["Gulnoza", 22], ["Hasan", 28]]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
users = await db.select("users")
|
|
175
|
+
print(users)
|
|
176
|
+
|
|
177
|
+
user = await db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
178
|
+
print(user)
|
|
179
|
+
|
|
180
|
+
adults = await db.select("users", where={"age__gt": 18})
|
|
181
|
+
print(adults)
|
|
182
|
+
|
|
183
|
+
users_ilike = await db.select("users", where={"name__ilike": "%ali%"})
|
|
184
|
+
print(users_ilike)
|
|
185
|
+
|
|
186
|
+
await db.update("users", "age", 26, "name", "Ali")
|
|
187
|
+
|
|
188
|
+
await db.delete("users", "name", "Ali")
|
|
189
|
+
|
|
190
|
+
await db.drop("users", cascade=False)
|
|
191
|
+
|
|
192
|
+
result = await db.raw(
|
|
193
|
+
"SELECT name, age FROM users WHERE age > $1",
|
|
194
|
+
[25],
|
|
195
|
+
fetchall=True
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
print(result)
|
|
199
|
+
|
|
200
|
+
await db.close_pool()
|
|
201
|
+
|
|
202
|
+
asyncio.run(main())
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
# ORM foydalanish
|
|
208
|
+
|
|
209
|
+
## Sync ORM
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from postgresdb3 import PostgresDB
|
|
213
|
+
from postgresdb3.orm import Model
|
|
214
|
+
from postgresdb3.orm.fields import Serial, String, Integer, Text
|
|
215
|
+
|
|
216
|
+
db = PostgresDB(
|
|
217
|
+
database="mydb",
|
|
218
|
+
user="postgres",
|
|
219
|
+
password="mypassword"
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
class User(Model):
|
|
223
|
+
db = db
|
|
224
|
+
table = "users"
|
|
225
|
+
|
|
226
|
+
id = Serial(primary_key=True)
|
|
227
|
+
name = String()
|
|
228
|
+
age = Integer()
|
|
229
|
+
about = Text(nullable=True)
|
|
230
|
+
|
|
231
|
+
User.create_table()
|
|
232
|
+
|
|
233
|
+
user = User.create(name="Ali", age=20)
|
|
234
|
+
|
|
235
|
+
print(User.all())
|
|
236
|
+
|
|
237
|
+
user = User.find(1)
|
|
238
|
+
|
|
239
|
+
user.update(name="Valijon")
|
|
240
|
+
|
|
241
|
+
user.delete()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
# Qo‘shimcha
|
|
247
|
+
|
|
248
|
+
`%s` va `$1` bilan parametrizatsiya qilish xavfsiz va SQL injection'dan himoya qiladi.
|
|
249
|
+
|
|
250
|
+
`where` va `join` yordamida murakkab so‘rovlar yozish mumkin.
|
|
251
|
+
|
|
252
|
+
`limit`, `offset` va `order_by` parametrlaridan foydalanib ma'lumotlarni tartiblash va sahifalash oson.
|
|
253
|
+
|
|
254
|
+
`raw()` metodi orqali kerak bo‘lganda to‘g‘ridan-to‘g‘ri SQL so‘rov yozish mumkin.
|
|
255
|
+
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# PostgresDB3
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**PostgresDB3** — PostgreSQL bilan ishlash uchun oddiy, qulay va yengil Python wrapper.
|
|
8
|
+
Kutubxona sync va async ishlashni qo‘llab-quvvatlaydi hamda sodda ORM imkoniyatlarini ham taqdim etadi.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# O‘rnatish
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install postgresdb3
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Asosiy imkoniyatlar
|
|
21
|
+
|
|
22
|
+
* PostgreSQL bilan **sync va async** ishlash
|
|
23
|
+
* CRUD amallari
|
|
24
|
+
* `where`, `join`, `group_by`, `order_by`, `limit`, `offset`
|
|
25
|
+
* `like`, `ilike`, `in`, `isnull`, `gt`, `lt` kabi filterlar
|
|
26
|
+
* Raw SQL bajarish imkoniyati
|
|
27
|
+
* Sodda ORM tizimi
|
|
28
|
+
* Async ORM qo‘llab-quvvatlash
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Sync foydalanish
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from postgresdb3 import PostgresDB
|
|
36
|
+
|
|
37
|
+
db = PostgresDB(
|
|
38
|
+
database="mydb",
|
|
39
|
+
user="postgres",
|
|
40
|
+
password="mypassword",
|
|
41
|
+
host="localhost",
|
|
42
|
+
port=5432
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Jadval yaratish
|
|
46
|
+
db.create("users", "id SERIAL PRIMARY KEY, name VARCHAR(100), age INT")
|
|
47
|
+
|
|
48
|
+
# Bitta qator qo‘shish
|
|
49
|
+
db.insert("users", "name, age", ("Ali", 25))
|
|
50
|
+
|
|
51
|
+
# Bir nechta qator qo‘shish
|
|
52
|
+
db.insert_many(
|
|
53
|
+
"users",
|
|
54
|
+
"name, age",
|
|
55
|
+
[("Vali", 30), ("Gulnoza", 22), ("Hasan", 28)]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Barcha ma'lumotlarni olish
|
|
59
|
+
users = db.select("users")
|
|
60
|
+
print(users)
|
|
61
|
+
|
|
62
|
+
# Faqat kerakli ustunlarni olish
|
|
63
|
+
users = db.select("users", columns=["id", "name"])
|
|
64
|
+
print(users)
|
|
65
|
+
|
|
66
|
+
# Bitta qatorni olish
|
|
67
|
+
user = db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
68
|
+
print(user)
|
|
69
|
+
|
|
70
|
+
# Shart bilan qidirish
|
|
71
|
+
adults = db.select("users", where={"age__gt": 18})
|
|
72
|
+
print(adults)
|
|
73
|
+
|
|
74
|
+
# LIKE qidiruv
|
|
75
|
+
users_like = db.select("users", where={"name__like": "%Ali%"})
|
|
76
|
+
print(users_like)
|
|
77
|
+
|
|
78
|
+
# ILIKE qidiruv
|
|
79
|
+
users_ilike = db.select("users", where={"name__ilike": "%ali%"})
|
|
80
|
+
print(users_ilike)
|
|
81
|
+
|
|
82
|
+
# IN operatori
|
|
83
|
+
selected_users = db.select("users", where={"id__in": [1, 2, 3]})
|
|
84
|
+
print(selected_users)
|
|
85
|
+
|
|
86
|
+
# NULL tekshirish
|
|
87
|
+
users_with_name = db.select("users", where={"name__isnull": False})
|
|
88
|
+
print(users_with_name)
|
|
89
|
+
|
|
90
|
+
# Tartiblash va limit
|
|
91
|
+
ordered_users = db.select(
|
|
92
|
+
"users",
|
|
93
|
+
where={"age__gt": 18},
|
|
94
|
+
order_by="age DESC",
|
|
95
|
+
limit=2
|
|
96
|
+
)
|
|
97
|
+
print(ordered_users)
|
|
98
|
+
|
|
99
|
+
# Pagination
|
|
100
|
+
paged_users = db.select(
|
|
101
|
+
"users",
|
|
102
|
+
order_by="id ASC",
|
|
103
|
+
limit=2,
|
|
104
|
+
offset=2
|
|
105
|
+
)
|
|
106
|
+
print(paged_users)
|
|
107
|
+
|
|
108
|
+
# Ma'lumotni yangilash
|
|
109
|
+
db.update("users", "age", 26, "name", "Ali")
|
|
110
|
+
|
|
111
|
+
# Ma'lumotni o‘chirish
|
|
112
|
+
db.delete("users", "name", "Ali")
|
|
113
|
+
|
|
114
|
+
# Jadvalni o‘chirish
|
|
115
|
+
db.drop("users", cascade=False)
|
|
116
|
+
|
|
117
|
+
# Raw SQL bajarish
|
|
118
|
+
result = db.raw("SELECT name, age FROM users WHERE age > %s", [25], fetchall=True)
|
|
119
|
+
print(result)
|
|
120
|
+
|
|
121
|
+
# Connectionni yopish
|
|
122
|
+
db.close()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
# Async foydalanish
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import asyncio
|
|
131
|
+
from postgresdb3 import AsyncPostgresDB
|
|
132
|
+
|
|
133
|
+
async def main():
|
|
134
|
+
db = AsyncPostgresDB(
|
|
135
|
+
database="mydb",
|
|
136
|
+
user="postgres",
|
|
137
|
+
password="mypassword",
|
|
138
|
+
host="localhost",
|
|
139
|
+
port=5432
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
await db.create("users", "id SERIAL PRIMARY KEY, name VARCHAR(100), age INT")
|
|
143
|
+
|
|
144
|
+
await db.insert("users", "name, age", ["Ali", 25])
|
|
145
|
+
|
|
146
|
+
await db.insert_many(
|
|
147
|
+
"users",
|
|
148
|
+
"name, age",
|
|
149
|
+
[["Vali", 30], ["Gulnoza", 22], ["Hasan", 28]]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
users = await db.select("users")
|
|
153
|
+
print(users)
|
|
154
|
+
|
|
155
|
+
user = await db.select("users", where={"name": "Ali"}, fetchone=True)
|
|
156
|
+
print(user)
|
|
157
|
+
|
|
158
|
+
adults = await db.select("users", where={"age__gt": 18})
|
|
159
|
+
print(adults)
|
|
160
|
+
|
|
161
|
+
users_ilike = await db.select("users", where={"name__ilike": "%ali%"})
|
|
162
|
+
print(users_ilike)
|
|
163
|
+
|
|
164
|
+
await db.update("users", "age", 26, "name", "Ali")
|
|
165
|
+
|
|
166
|
+
await db.delete("users", "name", "Ali")
|
|
167
|
+
|
|
168
|
+
await db.drop("users", cascade=False)
|
|
169
|
+
|
|
170
|
+
result = await db.raw(
|
|
171
|
+
"SELECT name, age FROM users WHERE age > $1",
|
|
172
|
+
[25],
|
|
173
|
+
fetchall=True
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
print(result)
|
|
177
|
+
|
|
178
|
+
await db.close_pool()
|
|
179
|
+
|
|
180
|
+
asyncio.run(main())
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# ORM foydalanish
|
|
186
|
+
|
|
187
|
+
## Sync ORM
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from postgresdb3 import PostgresDB
|
|
191
|
+
from postgresdb3.orm import Model
|
|
192
|
+
from postgresdb3.orm.fields import Serial, String, Integer, Text
|
|
193
|
+
|
|
194
|
+
db = PostgresDB(
|
|
195
|
+
database="mydb",
|
|
196
|
+
user="postgres",
|
|
197
|
+
password="mypassword"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
class User(Model):
|
|
201
|
+
db = db
|
|
202
|
+
table = "users"
|
|
203
|
+
|
|
204
|
+
id = Serial(primary_key=True)
|
|
205
|
+
name = String()
|
|
206
|
+
age = Integer()
|
|
207
|
+
about = Text(nullable=True)
|
|
208
|
+
|
|
209
|
+
User.create_table()
|
|
210
|
+
|
|
211
|
+
user = User.create(name="Ali", age=20)
|
|
212
|
+
|
|
213
|
+
print(User.all())
|
|
214
|
+
|
|
215
|
+
user = User.find(1)
|
|
216
|
+
|
|
217
|
+
user.update(name="Valijon")
|
|
218
|
+
|
|
219
|
+
user.delete()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
# Qo‘shimcha
|
|
225
|
+
|
|
226
|
+
`%s` va `$1` bilan parametrizatsiya qilish xavfsiz va SQL injection'dan himoya qiladi.
|
|
227
|
+
|
|
228
|
+
`where` va `join` yordamida murakkab so‘rovlar yozish mumkin.
|
|
229
|
+
|
|
230
|
+
`limit`, `offset` va `order_by` parametrlaridan foydalanib ma'lumotlarni tartiblash va sahifalash oson.
|
|
231
|
+
|
|
232
|
+
`raw()` metodi orqali kerak bo‘lganda to‘g‘ridan-to‘g‘ri SQL so‘rov yozish mumkin.
|
|
233
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .core import PostgresDB, AsyncPostgresDB
|
|
@@ -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)
|