ul-db-utils 2.10.7__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.
- ul-db-utils-2.10.7/LICENSE +0 -0
- ul-db-utils-2.10.7/PKG-INFO +147 -0
- ul-db-utils-2.10.7/README.md +128 -0
- ul-db-utils-2.10.7/setup.cfg +4 -0
- ul-db-utils-2.10.7/setup.py +69 -0
- ul-db-utils-2.10.7/ul_db_utils/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/cmd_action.py +37 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/cmd_docs.py +60 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/cmd_dump.py +42 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/cmd_restore.py +109 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/cmd_waiting.py +26 -0
- ul-db-utils-2.10.7/ul_db_utils/commands/doc_request_sql.sql +16 -0
- ul-db-utils-2.10.7/ul_db_utils/conf.py +6 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/compare_null_error.py +9 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/db_error.py +2 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/db_filter_error.py +6 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/db_sort_error.py +6 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/deletion_not_allowed.py +7 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/multiple_objects_returned.py +7 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/unknow_field_error.py +13 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/update_column_not_allowed_error.py +7 -0
- ul-db-utils-2.10.7/ul_db_utils/errors/update_not_allowed.py +7 -0
- ul-db-utils-2.10.7/ul_db_utils/main.py +33 -0
- ul-db-utils-2.10.7/ul_db_utils/model/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/model/api_user.py +14 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_api_user_log_model.py +124 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_immutable_model.py +67 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_mater_pg_view.py +137 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_model.py +92 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_undeletable_model.py +88 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_undeletable_user_log_model.py +103 -0
- ul-db-utils-2.10.7/ul_db_utils/model/base_user_log_model.py +117 -0
- ul-db-utils-2.10.7/ul_db_utils/model/media_storage/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file.py +36 -0
- ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file_download_link.py +35 -0
- ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file_type.py +23 -0
- ul-db-utils-2.10.7/ul_db_utils/model/methods/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/model/methods/make_immutable_column.py +14 -0
- ul-db-utils-2.10.7/ul_db_utils/model/referense_link.py +18 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/audit.sql +251 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/audit_manager.py +59 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/custom_query.py +57 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/db.py +167 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/db_context.py +18 -0
- ul-db-utils-2.10.7/ul_db_utils/modules/transaction_commit.py +20 -0
- ul-db-utils-2.10.7/ul_db_utils/py.typed +0 -0
- ul-db-utils-2.10.7/ul_db_utils/search/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/search/db_search.py +310 -0
- ul-db-utils-2.10.7/ul_db_utils/search/helpers.py +227 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/__init__.py +0 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_bool.py +3 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_choices.py +12 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys.py +11 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys_choice.py +12 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys_strict.py +14 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_str_keys.py +8 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_upper_keys.py +9 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_float.py +3 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_int.py +3 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_int_positive.py +13 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_len.py +6 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_list.py +10 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_list_of.py +11 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_positive_int_non_zero.py +4 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_set.py +8 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_str.py +3 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_type.py +8 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_url_with_scheme_and_netloc.py +13 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/ensure_db_object_exists.py +11 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/get_model_template.py +24 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/query_soft_delete.py +48 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/remove_duplicated_spaces_of_string.py +3 -0
- ul-db-utils-2.10.7/ul_db_utils/utils/waiting_for_postgres.py +29 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/PKG-INFO +147 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/SOURCES.txt +81 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/dependency_links.txt +1 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/entry_points.txt +2 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/requires.txt +21 -0
- ul-db-utils-2.10.7/ul_db_utils.egg-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ul-db-utils
|
|
3
|
+
Version: 2.10.7
|
|
4
|
+
Summary: Python ul db utils
|
|
5
|
+
Home-page: https://gitlab.neroelectronics.by/unic-lab/libraries/common-python-utils/db-utils.git
|
|
6
|
+
Author: Unic-lab
|
|
7
|
+
Author-email:
|
|
8
|
+
Platform: any
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
|
|
20
|
+
# Generic library db-utils
|
|
21
|
+
|
|
22
|
+
> Provides common database-related functionality that can be used across different services.
|
|
23
|
+
|
|
24
|
+
> Contains all database-related packages as dependencies.
|
|
25
|
+
If you need to use some package that is not available in your service, you should add it here.
|
|
26
|
+
|
|
27
|
+
## Common functionality
|
|
28
|
+
> This section describes some classes or methods that are awailable for use in all services that use db-utils.
|
|
29
|
+
|
|
30
|
+
### CustomQuery
|
|
31
|
+
> As a default this class inherit from *flask_sqlalchemy BaseQuery* and adds additional filters.
|
|
32
|
+
> 1. Filtering by only non-deleted (marked as *is_alive=True*) records.
|
|
33
|
+
> 2. Joining with/without deleted (marked as *is_alive=False*) records.
|
|
34
|
+
|
|
35
|
+
> If you want to add some additional by-default behavior to all services than you have to add it here.
|
|
36
|
+
|
|
37
|
+
### transaction_commit
|
|
38
|
+
> This context manager allows us to perform a database transaction at ORM level. You should use it like this:
|
|
39
|
+
> ```python
|
|
40
|
+
> with transaction_commit():
|
|
41
|
+
> query.perform(something)
|
|
42
|
+
|
|
43
|
+
### Abstract models
|
|
44
|
+
> This section describes all available abstract models from which we can inherit in our services.
|
|
45
|
+
|
|
46
|
+
#### BaseModel
|
|
47
|
+
```python
|
|
48
|
+
class BaseModel(DbModel, SerializerMixin):
|
|
49
|
+
|
|
50
|
+
__abstract__ = True
|
|
51
|
+
|
|
52
|
+
query_class = CustomQuery
|
|
53
|
+
|
|
54
|
+
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
55
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
56
|
+
date_modified = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
57
|
+
is_alive = db.Column(db.Boolean(), default=True, nullable=False)
|
|
58
|
+
```
|
|
59
|
+
> Provides UUID, record creation/modification datetime and is_alive field used for soft-deletion.
|
|
60
|
+
|
|
61
|
+
#### BaseUndeletableModel
|
|
62
|
+
```python
|
|
63
|
+
class BaseUndeletableModel(DbModel, SerializerMixin):
|
|
64
|
+
|
|
65
|
+
__abstract__ = True
|
|
66
|
+
|
|
67
|
+
query_class = BaseQuery
|
|
68
|
+
|
|
69
|
+
id = db.Column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
70
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
71
|
+
date_modified = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
72
|
+
```
|
|
73
|
+
> The same thing as BaseModel but models that will inherit from this model won't be able to soft-delete records.
|
|
74
|
+
|
|
75
|
+
#### BaseMaterializedPGViewModel
|
|
76
|
+
```python
|
|
77
|
+
class BaseMaterializedPGView(DbModel, SerializerMixin):
|
|
78
|
+
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
79
|
+
last_refresh_date = db.Column(db.DateTime(), nullable=False)
|
|
80
|
+
|
|
81
|
+
sql = ''
|
|
82
|
+
refresh_by_tables: List[str] = []
|
|
83
|
+
|
|
84
|
+
query_class = BaseQuery
|
|
85
|
+
|
|
86
|
+
__table_args__ = {'info': {'skip_autogenerate': True}}
|
|
87
|
+
|
|
88
|
+
_index_format = '{table}_{field}_index'
|
|
89
|
+
_pkey_format = '{table}_pkey'
|
|
90
|
+
|
|
91
|
+
__abstract__ = True
|
|
92
|
+
```
|
|
93
|
+
> Provides a way to create materialized views in PostgreSQL, add indexes, triggers, etc.
|
|
94
|
+
|
|
95
|
+
#### BaseImmutableModel
|
|
96
|
+
```python
|
|
97
|
+
class BaseImmutableModel(DbModel, SerializerMixin):
|
|
98
|
+
__abstract__ = True
|
|
99
|
+
|
|
100
|
+
query_class = BaseQuery
|
|
101
|
+
|
|
102
|
+
id = db.Column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
103
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
104
|
+
user_created_id = db.Column(PG_UUID(as_uuid=True), nullable=False)
|
|
105
|
+
```
|
|
106
|
+
> Models that are going to inherit from this one won't have update record functionality.
|
|
107
|
+
|
|
108
|
+
#### ApiUser
|
|
109
|
+
```python
|
|
110
|
+
class ApiUser(BaseModel):
|
|
111
|
+
__tablename__ = 'api_user'
|
|
112
|
+
|
|
113
|
+
date_expiration = db.Column(db.DateTime(), nullable=False)
|
|
114
|
+
name = db.Column(db.String(255), unique=True, nullable=False)
|
|
115
|
+
note = db.Column(db.Text(), nullable=False)
|
|
116
|
+
permissions = db.Column(ARRAY(db.Integer()), nullable=False)
|
|
117
|
+
```
|
|
118
|
+
> Every APIUser Model record has an array of permissions.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### Exceptions
|
|
122
|
+
|
|
123
|
+
| Exception | Desription |
|
|
124
|
+
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
125
|
+
| ComparisonToNullError | Raised when a client attempts to use a filter object that compares a resource's attribute to NULL using == operator instead of using is_null. |
|
|
126
|
+
| DBFiltersError | Raised when a client attempts to filter with invalid query params. |
|
|
127
|
+
| DBSortError | Raised when a client attempts to sort with invalid query params. |
|
|
128
|
+
| MultipleObjectsReturnedError | When only one object expected to be returned, but DB returned multiple objects. |
|
|
129
|
+
| UnknownFieldError | When user tries to reference the non-existent model field. |
|
|
130
|
+
| DeletionNotAllowedError | Raised when db obj deletion not allowed. |
|
|
131
|
+
| UpdateColumnNotAllowedError | Raised when db table column update not allowed. |
|
|
132
|
+
| UpdateNotAllowedError | Raised when db table update not allowed. |
|
|
133
|
+
| DbError | Generic DB error. |
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
## Adding new database-related package
|
|
137
|
+
> First, try to understand why do you need this library and what exactly can you do with it. Look at the list of
|
|
138
|
+
> already existing libraries and think if they can fulfill your needs.
|
|
139
|
+
|
|
140
|
+
> Check this library for deprecation, does it have enough maintenance, library dependencies.
|
|
141
|
+
> If all above satisfies you, perform next steps:
|
|
142
|
+
> 1. Add the package name and version to **Pipfile** under ```[packages]``` section. Example: ```alembic = "==1.8.1"```.
|
|
143
|
+
> 2. Run ```pipenv install```.
|
|
144
|
+
> 3. Add the package name and version to **setup.py** to ```install-requires``` section.
|
|
145
|
+
> 4. Commit changes. ```git commit -m "Add dependency *library-name*"```.
|
|
146
|
+
> 5. Run version patch: ```pipenv run version_patch```.
|
|
147
|
+
> 6. Push changes directly to dev ```git push origin dev --tags``` or raise MR for your changes to be reviewed.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Generic library db-utils
|
|
2
|
+
|
|
3
|
+
> Provides common database-related functionality that can be used across different services.
|
|
4
|
+
|
|
5
|
+
> Contains all database-related packages as dependencies.
|
|
6
|
+
If you need to use some package that is not available in your service, you should add it here.
|
|
7
|
+
|
|
8
|
+
## Common functionality
|
|
9
|
+
> This section describes some classes or methods that are awailable for use in all services that use db-utils.
|
|
10
|
+
|
|
11
|
+
### CustomQuery
|
|
12
|
+
> As a default this class inherit from *flask_sqlalchemy BaseQuery* and adds additional filters.
|
|
13
|
+
> 1. Filtering by only non-deleted (marked as *is_alive=True*) records.
|
|
14
|
+
> 2. Joining with/without deleted (marked as *is_alive=False*) records.
|
|
15
|
+
|
|
16
|
+
> If you want to add some additional by-default behavior to all services than you have to add it here.
|
|
17
|
+
|
|
18
|
+
### transaction_commit
|
|
19
|
+
> This context manager allows us to perform a database transaction at ORM level. You should use it like this:
|
|
20
|
+
> ```python
|
|
21
|
+
> with transaction_commit():
|
|
22
|
+
> query.perform(something)
|
|
23
|
+
|
|
24
|
+
### Abstract models
|
|
25
|
+
> This section describes all available abstract models from which we can inherit in our services.
|
|
26
|
+
|
|
27
|
+
#### BaseModel
|
|
28
|
+
```python
|
|
29
|
+
class BaseModel(DbModel, SerializerMixin):
|
|
30
|
+
|
|
31
|
+
__abstract__ = True
|
|
32
|
+
|
|
33
|
+
query_class = CustomQuery
|
|
34
|
+
|
|
35
|
+
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
36
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
37
|
+
date_modified = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
38
|
+
is_alive = db.Column(db.Boolean(), default=True, nullable=False)
|
|
39
|
+
```
|
|
40
|
+
> Provides UUID, record creation/modification datetime and is_alive field used for soft-deletion.
|
|
41
|
+
|
|
42
|
+
#### BaseUndeletableModel
|
|
43
|
+
```python
|
|
44
|
+
class BaseUndeletableModel(DbModel, SerializerMixin):
|
|
45
|
+
|
|
46
|
+
__abstract__ = True
|
|
47
|
+
|
|
48
|
+
query_class = BaseQuery
|
|
49
|
+
|
|
50
|
+
id = db.Column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
51
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
52
|
+
date_modified = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
53
|
+
```
|
|
54
|
+
> The same thing as BaseModel but models that will inherit from this model won't be able to soft-delete records.
|
|
55
|
+
|
|
56
|
+
#### BaseMaterializedPGViewModel
|
|
57
|
+
```python
|
|
58
|
+
class BaseMaterializedPGView(DbModel, SerializerMixin):
|
|
59
|
+
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
60
|
+
last_refresh_date = db.Column(db.DateTime(), nullable=False)
|
|
61
|
+
|
|
62
|
+
sql = ''
|
|
63
|
+
refresh_by_tables: List[str] = []
|
|
64
|
+
|
|
65
|
+
query_class = BaseQuery
|
|
66
|
+
|
|
67
|
+
__table_args__ = {'info': {'skip_autogenerate': True}}
|
|
68
|
+
|
|
69
|
+
_index_format = '{table}_{field}_index'
|
|
70
|
+
_pkey_format = '{table}_pkey'
|
|
71
|
+
|
|
72
|
+
__abstract__ = True
|
|
73
|
+
```
|
|
74
|
+
> Provides a way to create materialized views in PostgreSQL, add indexes, triggers, etc.
|
|
75
|
+
|
|
76
|
+
#### BaseImmutableModel
|
|
77
|
+
```python
|
|
78
|
+
class BaseImmutableModel(DbModel, SerializerMixin):
|
|
79
|
+
__abstract__ = True
|
|
80
|
+
|
|
81
|
+
query_class = BaseQuery
|
|
82
|
+
|
|
83
|
+
id = db.Column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
84
|
+
date_created = db.Column(db.DateTime(), default=datetime.utcnow(), nullable=False)
|
|
85
|
+
user_created_id = db.Column(PG_UUID(as_uuid=True), nullable=False)
|
|
86
|
+
```
|
|
87
|
+
> Models that are going to inherit from this one won't have update record functionality.
|
|
88
|
+
|
|
89
|
+
#### ApiUser
|
|
90
|
+
```python
|
|
91
|
+
class ApiUser(BaseModel):
|
|
92
|
+
__tablename__ = 'api_user'
|
|
93
|
+
|
|
94
|
+
date_expiration = db.Column(db.DateTime(), nullable=False)
|
|
95
|
+
name = db.Column(db.String(255), unique=True, nullable=False)
|
|
96
|
+
note = db.Column(db.Text(), nullable=False)
|
|
97
|
+
permissions = db.Column(ARRAY(db.Integer()), nullable=False)
|
|
98
|
+
```
|
|
99
|
+
> Every APIUser Model record has an array of permissions.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
### Exceptions
|
|
103
|
+
|
|
104
|
+
| Exception | Desription |
|
|
105
|
+
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
106
|
+
| ComparisonToNullError | Raised when a client attempts to use a filter object that compares a resource's attribute to NULL using == operator instead of using is_null. |
|
|
107
|
+
| DBFiltersError | Raised when a client attempts to filter with invalid query params. |
|
|
108
|
+
| DBSortError | Raised when a client attempts to sort with invalid query params. |
|
|
109
|
+
| MultipleObjectsReturnedError | When only one object expected to be returned, but DB returned multiple objects. |
|
|
110
|
+
| UnknownFieldError | When user tries to reference the non-existent model field. |
|
|
111
|
+
| DeletionNotAllowedError | Raised when db obj deletion not allowed. |
|
|
112
|
+
| UpdateColumnNotAllowedError | Raised when db table column update not allowed. |
|
|
113
|
+
| UpdateNotAllowedError | Raised when db table update not allowed. |
|
|
114
|
+
| DbError | Generic DB error. |
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Adding new database-related package
|
|
118
|
+
> First, try to understand why do you need this library and what exactly can you do with it. Look at the list of
|
|
119
|
+
> already existing libraries and think if they can fulfill your needs.
|
|
120
|
+
|
|
121
|
+
> Check this library for deprecation, does it have enough maintenance, library dependencies.
|
|
122
|
+
> If all above satisfies you, perform next steps:
|
|
123
|
+
> 1. Add the package name and version to **Pipfile** under ```[packages]``` section. Example: ```alembic = "==1.8.1"```.
|
|
124
|
+
> 2. Run ```pipenv install```.
|
|
125
|
+
> 3. Add the package name and version to **setup.py** to ```install-requires``` section.
|
|
126
|
+
> 4. Commit changes. ```git commit -m "Add dependency *library-name*"```.
|
|
127
|
+
> 5. Run version patch: ```pipenv run version_patch```.
|
|
128
|
+
> 6. Push changes directly to dev ```git push origin dev --tags``` or raise MR for your changes to be reviewed.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
HERE = path.abspath(path.dirname(__file__))
|
|
5
|
+
|
|
6
|
+
with open(path.join(HERE, 'README.md'), encoding='utf-8') as f:
|
|
7
|
+
long_description = f.read()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
setup(
|
|
11
|
+
name='ul-db-utils',
|
|
12
|
+
version='2.10.7',
|
|
13
|
+
description='Python ul db utils',
|
|
14
|
+
author='Unic-lab',
|
|
15
|
+
author_email='',
|
|
16
|
+
url='https://gitlab.neroelectronics.by/unic-lab/libraries/common-python-utils/db-utils.git',
|
|
17
|
+
packages=find_packages(include=['ul_db_utils*']),
|
|
18
|
+
platforms='any',
|
|
19
|
+
package_data={
|
|
20
|
+
'': ['*.sql'],
|
|
21
|
+
'ul_db_utils': ['py.typed'],
|
|
22
|
+
},
|
|
23
|
+
long_description=long_description,
|
|
24
|
+
long_description_content_type="text/markdown",
|
|
25
|
+
classifiers=[
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python",
|
|
29
|
+
"Programming Language :: Python :: 3.6",
|
|
30
|
+
"Programming Language :: Python :: 3.7",
|
|
31
|
+
"Programming Language :: Python :: 3.8",
|
|
32
|
+
"Programming Language :: Python :: 3.9",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
],
|
|
35
|
+
entry_points={
|
|
36
|
+
"console_scripts": [
|
|
37
|
+
'uldbutls=ul_db_utils.main:main',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
include_package_data=True,
|
|
41
|
+
install_requires=[
|
|
42
|
+
"flask==2.1.3", # FOR COMPATIBILITY
|
|
43
|
+
"py-dateutil==2.2",
|
|
44
|
+
|
|
45
|
+
"psycopg2-binary==2.9.5",
|
|
46
|
+
"flask-sqlalchemy==2.5.1",
|
|
47
|
+
"flask-migrate==3.1.0",
|
|
48
|
+
"sqlalchemy[mypy]==1.4.41",
|
|
49
|
+
"sqlalchemy-stubs==0.4",
|
|
50
|
+
"sqlalchemy-utils==0.38.3",
|
|
51
|
+
"sqlalchemy-serializer==1.4.1",
|
|
52
|
+
"alembic==1.8.1",
|
|
53
|
+
"mysql-connector-python==8.0.31",
|
|
54
|
+
|
|
55
|
+
"redis==4.3.4",
|
|
56
|
+
|
|
57
|
+
"types-psycopg2==2.9.18",
|
|
58
|
+
"sqlalchemy-stubs==0.4",
|
|
59
|
+
"types-flask-sqlalchemy==2.5.3",
|
|
60
|
+
"types-sqlalchemy-utils==1.0.1",
|
|
61
|
+
"types-sqlalchemy==1.4.40",
|
|
62
|
+
"types-redis==4.3.13",
|
|
63
|
+
"types-jinja2==2.11.9",
|
|
64
|
+
"types-python-dateutil==2.8.19",
|
|
65
|
+
# "types-requests==2.28.8",
|
|
66
|
+
|
|
67
|
+
"ul-py-tool==1.15.20",
|
|
68
|
+
],
|
|
69
|
+
)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CmdAction(Cmd):
|
|
11
|
+
app_dir: str
|
|
12
|
+
app_migration_dir_name: str
|
|
13
|
+
app_file_name: str
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def app_rel_dir(self) -> str:
|
|
17
|
+
return os.path.relpath(self.app_dir, os.getcwd())
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def add_parser_args(parser: ArgumentParser) -> None:
|
|
21
|
+
parser.add_argument('--app-dir', dest='app_dir', type=str, required=True)
|
|
22
|
+
parser.add_argument('--app-migration-dir-name', dest='app_migration_dir_name', type=str, default='migrations', required=False)
|
|
23
|
+
parser.add_argument('--app-flask-file-name', dest='app_file_name', type=str, default='main.py', required=False)
|
|
24
|
+
|
|
25
|
+
def run(self, *args: Any, **kwargs: Any) -> None:
|
|
26
|
+
migration_dir = os.path.join(self.app_dir, self.app_migration_dir_name)
|
|
27
|
+
flask_app_path = os.path.join(self.app_dir, self.app_file_name)
|
|
28
|
+
additional_args = ' '.join([f'{key}={value}' for key, value in kwargs.items()])
|
|
29
|
+
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
[f'FLASK_APP="{flask_app_path}" flask db {self.cmd} {additional_args} --directory "{migration_dir}"'],
|
|
32
|
+
shell=True,
|
|
33
|
+
stderr=sys.stderr,
|
|
34
|
+
stdout=sys.stdout,
|
|
35
|
+
)
|
|
36
|
+
if result.returncode == 1:
|
|
37
|
+
exit(1)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from contextlib import closing
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import psycopg2
|
|
9
|
+
from pydantic import PostgresDsn
|
|
10
|
+
from sqlalchemy import text
|
|
11
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CmdDocs(Cmd):
|
|
15
|
+
uri: PostgresDsn
|
|
16
|
+
dest_path: str
|
|
17
|
+
schema_db: str
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
21
|
+
def_dir = os.path.join(os.getcwd(), 'docs/db')
|
|
22
|
+
parser.add_argument('--db-uri', dest='uri', type=str, required=True)
|
|
23
|
+
parser.add_argument('--dest', dest='dest_path', type=str, default=def_dir, required=False)
|
|
24
|
+
parser.add_argument('--schema_db', dest='schema_db', type=str, default='public', required=False)
|
|
25
|
+
|
|
26
|
+
def run(self, *args: Any, **kwargs: Any) -> None:
|
|
27
|
+
parsed_db_uri = urlparse(self.uri)
|
|
28
|
+
db_name = parsed_db_uri.path.strip("/")
|
|
29
|
+
assert len(db_name) > 0
|
|
30
|
+
|
|
31
|
+
with open(os.path.join(os.path.dirname(__file__), 'doc_request_sql.sql'), 'rt') as sql_file:
|
|
32
|
+
doc_request_sql = sql_file.read()
|
|
33
|
+
|
|
34
|
+
doc_request_sql = text(doc_request_sql.format( # type: ignore
|
|
35
|
+
schema_db=self.schema_db,
|
|
36
|
+
))
|
|
37
|
+
|
|
38
|
+
with closing(psycopg2.connect(self.uri)) as conn:
|
|
39
|
+
with conn.cursor() as cursor:
|
|
40
|
+
cursor.execute(str(doc_request_sql))
|
|
41
|
+
doc_data = cursor.fetchall()
|
|
42
|
+
|
|
43
|
+
for item in doc_data:
|
|
44
|
+
db_name = item[0]
|
|
45
|
+
schema_name = item[1]
|
|
46
|
+
table_name = item[2]
|
|
47
|
+
table_description = item[3]
|
|
48
|
+
file_path = os.path.join(self.dest_path, self.clean_slug(db_name), f"{self.clean_slug(schema_name)}__{self.clean_slug(table_name)}.md")
|
|
49
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
50
|
+
with open(file_path, "w+") as file:
|
|
51
|
+
file.write(f"# {db_name} \"{schema_name}.{table_name}\"\n\n"
|
|
52
|
+
f"{table_description}\n\n"
|
|
53
|
+
f"## Описание колонок\n\n"
|
|
54
|
+
f"| Название | Тип | Описание |\n"
|
|
55
|
+
f"| -------- | --- | -------- |\n")
|
|
56
|
+
for table_field_name, table_field_type, table_field_description in zip(item[4], item[5], item[6]):
|
|
57
|
+
file.write(f"| {table_field_name} | {table_field_type} | {table_field_description} |\n")
|
|
58
|
+
|
|
59
|
+
def clean_slug(self, text: str) -> str:
|
|
60
|
+
return re.sub(r"[^\w\d]+", "_", text)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CmdDump(Cmd):
|
|
13
|
+
uri: str
|
|
14
|
+
dest_path: str
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def add_parser_args(parser: ArgumentParser) -> None:
|
|
18
|
+
def_file = os.path.join(os.getcwd(), '.tmp', f'dbdump_{datetime.now().isoformat()}.sql')
|
|
19
|
+
parser.add_argument('--db-uri', dest='uri', type=str, required=True)
|
|
20
|
+
parser.add_argument('--dest', dest='dest_path', type=str, default=def_file, required=False)
|
|
21
|
+
|
|
22
|
+
def run(self, *args: Any, **kwargs: Any) -> None:
|
|
23
|
+
parsed_db_uri = urlparse(self.uri)
|
|
24
|
+
db_name = parsed_db_uri.path.strip("/")
|
|
25
|
+
assert len(db_name) > 0
|
|
26
|
+
os.makedirs(os.path.dirname(self.dest_path), exist_ok=True)
|
|
27
|
+
result = subprocess.run(
|
|
28
|
+
[(
|
|
29
|
+
f'PGPASSWORD={parsed_db_uri.password} pg_dump --schema=public --data-only -Fc '
|
|
30
|
+
f'-h {parsed_db_uri.hostname} '
|
|
31
|
+
f'-p {parsed_db_uri.port} '
|
|
32
|
+
f'-U {parsed_db_uri.username} '
|
|
33
|
+
f'-d {db_name} '
|
|
34
|
+
f'-f {self.dest_path} '
|
|
35
|
+
)],
|
|
36
|
+
shell=True,
|
|
37
|
+
check=True,
|
|
38
|
+
stderr=sys.stderr,
|
|
39
|
+
stdout=sys.stdout,
|
|
40
|
+
)
|
|
41
|
+
if result.returncode == 1:
|
|
42
|
+
exit(1)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CmdRestore(Cmd):
|
|
12
|
+
uri: str
|
|
13
|
+
db_dump_file_path: str
|
|
14
|
+
flags: str
|
|
15
|
+
clean_data: int
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
19
|
+
db_dump_path = os.path.join(os.getcwd(), '.tmp', 'dbdump.sql')
|
|
20
|
+
parser.add_argument('--db-uri', dest='uri', type=str, required=True)
|
|
21
|
+
parser.add_argument('--dump-file', dest='db_dump_file_path', type=str, default=db_dump_path, required=False)
|
|
22
|
+
parser.add_argument('--clean-data', dest='clean_data', type=int, default=1, required=False)
|
|
23
|
+
parser.add_argument('--flags', dest='flags', type=str, default='', required=False)
|
|
24
|
+
|
|
25
|
+
def run(self, *args: Any, **kwargs: Any) -> None:
|
|
26
|
+
parsed_db_uri = urlparse(self.uri)
|
|
27
|
+
db_name = parsed_db_uri.path.strip("/")
|
|
28
|
+
if not os.path.exists(self.db_dump_file_path):
|
|
29
|
+
raise ValueError(f'file {self.db_dump_file_path} was not found')
|
|
30
|
+
|
|
31
|
+
db_pref = f'-U {parsed_db_uri.username} -d {db_name} -h {parsed_db_uri.hostname} -p {parsed_db_uri.port}'
|
|
32
|
+
db_pwd = f'PGPASSWORD={parsed_db_uri.password}'
|
|
33
|
+
|
|
34
|
+
subprocess.run(
|
|
35
|
+
[f'{db_pwd} psql {db_pref} -c "create user replicator; "'],
|
|
36
|
+
shell=True,
|
|
37
|
+
check=False,
|
|
38
|
+
stderr=sys.stderr,
|
|
39
|
+
stdout=sys.stdout,
|
|
40
|
+
)
|
|
41
|
+
subprocess.run(
|
|
42
|
+
[f'{db_pwd} psql {db_pref} -c "create user postgres;"'],
|
|
43
|
+
shell=True,
|
|
44
|
+
check=False,
|
|
45
|
+
stderr=sys.stderr,
|
|
46
|
+
stdout=sys.stdout,
|
|
47
|
+
)
|
|
48
|
+
subprocess.run(
|
|
49
|
+
[f'{db_pwd} psql {db_pref} -c "create user viewer;"'],
|
|
50
|
+
shell=True,
|
|
51
|
+
check=False,
|
|
52
|
+
stderr=sys.stderr,
|
|
53
|
+
stdout=sys.stdout,
|
|
54
|
+
)
|
|
55
|
+
subprocess.run(
|
|
56
|
+
[f'{db_pwd} psql {db_pref} -c "create schema audit;"'],
|
|
57
|
+
shell=True,
|
|
58
|
+
check=False,
|
|
59
|
+
stderr=sys.stderr,
|
|
60
|
+
stdout=sys.stdout,
|
|
61
|
+
)
|
|
62
|
+
subprocess.run(
|
|
63
|
+
[f'{db_pwd} psql {db_pref} -c "create schema public;"'],
|
|
64
|
+
shell=True,
|
|
65
|
+
check=False,
|
|
66
|
+
stderr=sys.stderr,
|
|
67
|
+
stdout=sys.stdout,
|
|
68
|
+
)
|
|
69
|
+
subprocess.run(
|
|
70
|
+
[f'{db_pwd} psql {db_pref} -c "create schema cache;"'],
|
|
71
|
+
shell=True,
|
|
72
|
+
check=False,
|
|
73
|
+
stderr=sys.stderr,
|
|
74
|
+
stdout=sys.stdout,
|
|
75
|
+
)
|
|
76
|
+
subprocess.run(
|
|
77
|
+
[f'{db_pwd} psql {db_pref} -c "drop extension timescaledb;"'],
|
|
78
|
+
shell=True,
|
|
79
|
+
check=False,
|
|
80
|
+
stderr=sys.stderr,
|
|
81
|
+
stdout=sys.stdout,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if self.clean_data:
|
|
85
|
+
truncate_all_sql = (
|
|
86
|
+
f"CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS \\$$ "
|
|
87
|
+
f"DECLARE statements CURSOR FOR SELECT tablename FROM pg_tables WHERE tableowner = username AND schemaname = 'public'; "
|
|
88
|
+
f"BEGIN FOR stmt IN statements LOOP EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;'; END LOOP; END; "
|
|
89
|
+
f"\\$$ LANGUAGE plpgsql; "
|
|
90
|
+
f"SELECT truncate_tables('{parsed_db_uri.username}');"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
subprocess.run(
|
|
94
|
+
[f'{db_pwd} psql {db_pref} -c "{truncate_all_sql}"'],
|
|
95
|
+
shell=True,
|
|
96
|
+
check=True,
|
|
97
|
+
stderr=sys.stderr,
|
|
98
|
+
stdout=sys.stdout,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[f'{db_pwd} pg_restore {db_pref} --data-only --exclude-schema=audit {self.flags} < "{self.db_dump_file_path}"'],
|
|
103
|
+
shell=True,
|
|
104
|
+
check=True,
|
|
105
|
+
stderr=sys.stderr,
|
|
106
|
+
stdout=sys.stdout,
|
|
107
|
+
)
|
|
108
|
+
if result.returncode == 1:
|
|
109
|
+
exit(1)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
6
|
+
|
|
7
|
+
from ul_db_utils.utils.waiting_for_postgres import waiting_for_postgres
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CmdWaiting(Cmd):
|
|
13
|
+
uri: str
|
|
14
|
+
max_times: int
|
|
15
|
+
delay: int
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
19
|
+
parser.add_argument('--db-uri', dest='uri', type=str, required=True)
|
|
20
|
+
parser.add_argument('--retry-times', dest='max_times', type=int, default=100, required=False)
|
|
21
|
+
parser.add_argument('--retry-delay-sec', dest='delay', type=int, default=1, required=False)
|
|
22
|
+
|
|
23
|
+
def run(self, *args: Any, **kwargs: Any) -> None:
|
|
24
|
+
if not waiting_for_postgres(self.uri, retry_max_count=self.max_times, retry_delay_s=float(self.delay)):
|
|
25
|
+
exit(2)
|
|
26
|
+
exit(0)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
SELECT
|
|
2
|
+
current_database() as "db_name",
|
|
3
|
+
pgns.nspname as "schema_name",
|
|
4
|
+
tbl.relname as "table_name",
|
|
5
|
+
obj_description(tbl.oid, 'pg_class') as "table_description",
|
|
6
|
+
ARRAY_AGG(pgattr.attname) as "table_field_name",
|
|
7
|
+
ARRAY_AGG(pgtype.typname) as "table_field_type",
|
|
8
|
+
ARRAY_AGG(col_description(tbl.oid, pgattr.attnum)) as "table_field_description"
|
|
9
|
+
FROM pg_class tbl
|
|
10
|
+
LEFT OUTER JOIN pg_catalog.pg_namespace pgns ON pgns.nspname = '{schema_db}'
|
|
11
|
+
LEFT OUTER JOIN pg_attribute pgattr ON pgattr.attrelid = tbl.oid AND pgattr.attnum > 0 AND pgattr.atttypid != 0
|
|
12
|
+
LEFT OUTER JOIN pg_type pgtype ON pgtype.oid = pgattr.atttypid
|
|
13
|
+
WHERE tbl.relnamespace = pgns.oid AND tbl.reltype != 0
|
|
14
|
+
GROUP BY
|
|
15
|
+
db_name, schema_name, table_name, table_description
|
|
16
|
+
ORDER BY table_name;
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from ul_db_utils.errors.db_error import DbError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ComparisonToNullError(DbError):
|
|
5
|
+
"""Raised when a client attempts to use a filter object that compares a
|
|
6
|
+
resource's attribute to ``NULL`` using the ``==`` operator instead of using
|
|
7
|
+
``is_null``.
|
|
8
|
+
"""
|
|
9
|
+
pass
|