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.
Files changed (83) hide show
  1. ul-db-utils-2.10.7/LICENSE +0 -0
  2. ul-db-utils-2.10.7/PKG-INFO +147 -0
  3. ul-db-utils-2.10.7/README.md +128 -0
  4. ul-db-utils-2.10.7/setup.cfg +4 -0
  5. ul-db-utils-2.10.7/setup.py +69 -0
  6. ul-db-utils-2.10.7/ul_db_utils/__init__.py +0 -0
  7. ul-db-utils-2.10.7/ul_db_utils/commands/__init__.py +0 -0
  8. ul-db-utils-2.10.7/ul_db_utils/commands/cmd_action.py +37 -0
  9. ul-db-utils-2.10.7/ul_db_utils/commands/cmd_docs.py +60 -0
  10. ul-db-utils-2.10.7/ul_db_utils/commands/cmd_dump.py +42 -0
  11. ul-db-utils-2.10.7/ul_db_utils/commands/cmd_restore.py +109 -0
  12. ul-db-utils-2.10.7/ul_db_utils/commands/cmd_waiting.py +26 -0
  13. ul-db-utils-2.10.7/ul_db_utils/commands/doc_request_sql.sql +16 -0
  14. ul-db-utils-2.10.7/ul_db_utils/conf.py +6 -0
  15. ul-db-utils-2.10.7/ul_db_utils/errors/__init__.py +0 -0
  16. ul-db-utils-2.10.7/ul_db_utils/errors/compare_null_error.py +9 -0
  17. ul-db-utils-2.10.7/ul_db_utils/errors/db_error.py +2 -0
  18. ul-db-utils-2.10.7/ul_db_utils/errors/db_filter_error.py +6 -0
  19. ul-db-utils-2.10.7/ul_db_utils/errors/db_sort_error.py +6 -0
  20. ul-db-utils-2.10.7/ul_db_utils/errors/deletion_not_allowed.py +7 -0
  21. ul-db-utils-2.10.7/ul_db_utils/errors/multiple_objects_returned.py +7 -0
  22. ul-db-utils-2.10.7/ul_db_utils/errors/unknow_field_error.py +13 -0
  23. ul-db-utils-2.10.7/ul_db_utils/errors/update_column_not_allowed_error.py +7 -0
  24. ul-db-utils-2.10.7/ul_db_utils/errors/update_not_allowed.py +7 -0
  25. ul-db-utils-2.10.7/ul_db_utils/main.py +33 -0
  26. ul-db-utils-2.10.7/ul_db_utils/model/__init__.py +0 -0
  27. ul-db-utils-2.10.7/ul_db_utils/model/api_user.py +14 -0
  28. ul-db-utils-2.10.7/ul_db_utils/model/base_api_user_log_model.py +124 -0
  29. ul-db-utils-2.10.7/ul_db_utils/model/base_immutable_model.py +67 -0
  30. ul-db-utils-2.10.7/ul_db_utils/model/base_mater_pg_view.py +137 -0
  31. ul-db-utils-2.10.7/ul_db_utils/model/base_model.py +92 -0
  32. ul-db-utils-2.10.7/ul_db_utils/model/base_undeletable_model.py +88 -0
  33. ul-db-utils-2.10.7/ul_db_utils/model/base_undeletable_user_log_model.py +103 -0
  34. ul-db-utils-2.10.7/ul_db_utils/model/base_user_log_model.py +117 -0
  35. ul-db-utils-2.10.7/ul_db_utils/model/media_storage/__init__.py +0 -0
  36. ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file.py +36 -0
  37. ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file_download_link.py +35 -0
  38. ul-db-utils-2.10.7/ul_db_utils/model/media_storage/media_file_type.py +23 -0
  39. ul-db-utils-2.10.7/ul_db_utils/model/methods/__init__.py +0 -0
  40. ul-db-utils-2.10.7/ul_db_utils/model/methods/make_immutable_column.py +14 -0
  41. ul-db-utils-2.10.7/ul_db_utils/model/referense_link.py +18 -0
  42. ul-db-utils-2.10.7/ul_db_utils/modules/__init__.py +0 -0
  43. ul-db-utils-2.10.7/ul_db_utils/modules/audit.sql +251 -0
  44. ul-db-utils-2.10.7/ul_db_utils/modules/audit_manager.py +59 -0
  45. ul-db-utils-2.10.7/ul_db_utils/modules/custom_query.py +57 -0
  46. ul-db-utils-2.10.7/ul_db_utils/modules/db.py +167 -0
  47. ul-db-utils-2.10.7/ul_db_utils/modules/db_context.py +18 -0
  48. ul-db-utils-2.10.7/ul_db_utils/modules/transaction_commit.py +20 -0
  49. ul-db-utils-2.10.7/ul_db_utils/py.typed +0 -0
  50. ul-db-utils-2.10.7/ul_db_utils/search/__init__.py +0 -0
  51. ul-db-utils-2.10.7/ul_db_utils/search/db_search.py +310 -0
  52. ul-db-utils-2.10.7/ul_db_utils/search/helpers.py +227 -0
  53. ul-db-utils-2.10.7/ul_db_utils/utils/__init__.py +0 -0
  54. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/__init__.py +0 -0
  55. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_bool.py +3 -0
  56. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_choices.py +12 -0
  57. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys.py +11 -0
  58. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys_choice.py +12 -0
  59. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_keys_strict.py +14 -0
  60. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_str_keys.py +8 -0
  61. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_dict_upper_keys.py +9 -0
  62. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_float.py +3 -0
  63. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_int.py +3 -0
  64. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_int_positive.py +13 -0
  65. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_len.py +6 -0
  66. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_list.py +10 -0
  67. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_list_of.py +11 -0
  68. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_positive_int_non_zero.py +4 -0
  69. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_set.py +8 -0
  70. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_str.py +3 -0
  71. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_type.py +8 -0
  72. ul-db-utils-2.10.7/ul_db_utils/utils/ensure/ensure_url_with_scheme_and_netloc.py +13 -0
  73. ul-db-utils-2.10.7/ul_db_utils/utils/ensure_db_object_exists.py +11 -0
  74. ul-db-utils-2.10.7/ul_db_utils/utils/get_model_template.py +24 -0
  75. ul-db-utils-2.10.7/ul_db_utils/utils/query_soft_delete.py +48 -0
  76. ul-db-utils-2.10.7/ul_db_utils/utils/remove_duplicated_spaces_of_string.py +3 -0
  77. ul-db-utils-2.10.7/ul_db_utils/utils/waiting_for_postgres.py +29 -0
  78. ul-db-utils-2.10.7/ul_db_utils.egg-info/PKG-INFO +147 -0
  79. ul-db-utils-2.10.7/ul_db_utils.egg-info/SOURCES.txt +81 -0
  80. ul-db-utils-2.10.7/ul_db_utils.egg-info/dependency_links.txt +1 -0
  81. ul-db-utils-2.10.7/ul_db_utils.egg-info/entry_points.txt +2 -0
  82. ul-db-utils-2.10.7/ul_db_utils.egg-info/requires.txt +21 -0
  83. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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;
@@ -0,0 +1,6 @@
1
+ import os.path
2
+ from typing import Optional
3
+
4
+ THIS_LIBRARY_DIR = os.path.dirname(__file__)
5
+
6
+ APPLICATION__DB_URI: Optional[str] = os.environ.get('APPLICATION__DB_URI', None) # none only for backward compatibility
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