├── ReallySimpleDB ├── __init__.py ├── utils.py └── manager.py ├── assets └── images │ └── ReallySimpleDB.png ├── pyproject.toml ├── .github └── workflows │ ├── python-publish.yml │ └── tests.yml ├── setup.py ├── .gitignore ├── README.md └── tests └── test_manager.py /ReallySimpleDB/__init__.py: -------------------------------------------------------------------------------- 1 | from .manager import ReallySimpleDB as dbmanager 2 | -------------------------------------------------------------------------------- /assets/images/ReallySimpleDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truethari/ReallySimpleDB/HEAD/assets/images/ReallySimpleDB.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: "3.x" 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install setuptools wheel twine 21 | - name: Build and publish 22 | env: 23 | TWINE_USERNAME: __token__ 24 | TWINE_PASSWORD: ${{ secrets.TWINE_TOKEN }} 25 | run: | 26 | python setup.py sdist bdist_wheel 27 | twine upload dist/* 28 | -------------------------------------------------------------------------------- /ReallySimpleDB/utils.py: -------------------------------------------------------------------------------- 1 | DATA_TYPES = { 2 | "INT" : type(int()), 3 | "INTEGER" : type(int()), 4 | "TINYINT" : type(int()), 5 | "SMALLINT" : type(int()), 6 | "MEDIUMINT" : type(int()), 7 | "BIGINT" : type(int()), 8 | "UNSIGNED BIG INT" : type(int()), 9 | "INT2" : type(int()), 10 | "INT8" : type(int()), 11 | "CHARACTER(20)" : type(str()), 12 | "VARCHAR(255)" : type(str()), 13 | "VARYING CHARACTER(255)" : type(str()), 14 | "NCHAR(55)" : type(str()), 15 | "NATIVE CHARACTER(70)" : type(str()), 16 | "NVARCHAR(100)" : type(str()), 17 | "TEXT" : type(str()), 18 | "CLOB" : type(str()), 19 | "REAL" : type(float()), 20 | "DOUBLE" : type(float()), 21 | "DOUBLE PRECISION" : type(float()), 22 | "FLOAT" : type(float()), 23 | "BOOLEAN" : type(int()), 24 | "DATE" : type(str()), 25 | "DATETIME" : type(str()) 26 | } 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | from setuptools import setup 4 | 5 | here = pathlib.Path(__file__).parent.resolve() 6 | long_description = (here / 'README.md').read_text(encoding='utf-8') 7 | 8 | setup( 9 | name="ReallySimpleDB", 10 | version="1.2", 11 | description="A tool for easily manage databases with Python", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | author="tharindu.dev", 15 | author_email="tharindu.nm@yahoo.com", 16 | url="https://github.com/truethari/ReallySimpleDB", 17 | keywords="database sqlite python-database python-sqlite database-management", 18 | project_urls={ 19 | "Bug Tracker": "https://github.com/truethari/ReallySimpleDB/issues", 20 | }, 21 | classifiers=[ 22 | "Programming Language :: Python :: 3", 23 | ], 24 | packages=['ReallySimpleDB'], 25 | ) 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - beta 8 | - alpha 9 | paths-ignore: 10 | - "**/.gitignore" 11 | - "**/pyproject.toml" 12 | - "**/README.md" 13 | - "**/setup.py" 14 | pull_request: 15 | branches: [master] 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | python-version: [3.7, 3.8, 3.9] 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | python -m pip install pytest 34 | python setup.py install 35 | - name: Test with pytest 36 | run: | 37 | pytest tests 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
6 |
7 | [](https://github.com/truethari/ReallySimpleDB/actions/workflows/tests.yml) [](https://www.codacy.com/gh/truethari/ReallySimpleDB/dashboard?utm_source=github.com&utm_medium=referral&utm_content=truethari/ReallySimpleDB&utm_campaign=Badge_Grade) [](https://pypi.org/project/ReallySimpleDB/) [](https://www.python.org/) [](https://pepy.tech/project/reallysimpledb)
8 |
9 | ## What is This
10 |
11 | ---
12 |
13 | This is a Python application that can be used to manage **sqlite** databases without using any sql command.
14 |
15 | ## 🚀 Installation
16 |
17 | You can use pip:
18 |
19 | ```console
20 | ~$ pip3 install ReallySimpleDB
21 | ```
22 |
23 | or
24 |
25 | ```console
26 | ~$ python setup.py install
27 | ```
28 |
29 | ## 📗 Usage
30 |
31 | ```console
32 | >> from ReallySimpleDB import dbmanager
33 |
34 | >> _dbmanager = dbmanager()
35 | ```
36 |
37 | ### Create database
38 |
39 | ```console
40 | >> _dbmanager.create_db(dbpath="test.db", replace=True)
41 | ```
42 |
43 | ### Close connection
44 |
45 | ```console
46 | >> _dbmanager.close_connection()
47 | ```
48 |
49 | ### Create table
50 |
51 | Here you can not directly call the `create_table` function. Because **sqlite** cannot create table without columns. So you must first define the columns and create a table.
52 |
53 | **Important:** You have to close connection here. If not, code returns error. Because it tries to add column to existing table.
54 |
55 | ```console
56 | >> _dbmanager.close_connection()
57 | ```
58 |
59 | ```console
60 | >> _dbmanager.add_columns(column_name="student_id", primary_key=True)
61 | >> _dbmanager.add_columns(column_name="name", not_null=True)
62 | >> _dbmanager.add_columns(column_name="mark", datatype="INT")
63 |
64 | >> _dbmanager.create_table(database="test.db", table_name="STUDENTS")
65 | ```
66 |
67 | If you want to add columns to an existing table, read the **Add column to table** section.
68 |
69 | ### Get all tables
70 |
71 | ```console
72 | >> all_tables = _dbmanager.all_tables()
73 |
74 | ["STUDENT", "EXAM"]
75 | ```
76 |
77 | ### Check table if exists
78 |
79 | ```console
80 | >> _dbmanager.is_table(database="test.db", table_name="STUDENTS")
81 |
82 | True
83 | ```
84 |
85 | ### Delete table from database
86 |
87 | ```console
88 | >> _dbmanager.delete_table(table="STUDENTS")
89 | ```
90 |
91 | ### Add column to table
92 |
93 | ```console
94 | >> _dbmanager.add_columns(column_name="year", database="test.db", table="STUDENTS")
95 | ```
96 |
97 | ### Get all columns
98 |
99 | ```console
100 | >> _dbmanager.get_columns(table="STUDENTS")
101 |
102 | ["student_id", "name", "mark"]
103 | ```
104 |
105 | ### Get all columns with types
106 |
107 | ```console
108 | >> all_columns = _dbmanager.get_all_column_types(table="STUDENTS")
109 |
110 | {"student_id": "TEXT", "name": "TEXT", "mark": "INT"}
111 | ```
112 |
113 | ### Get columns type
114 |
115 | ```console
116 | >> _dbmanager.get_column_type(table="STUDENTS", column="student_id")
117 |
118 | "TEXT"
119 | ```
120 |
121 | ### Get primary key of a table
122 |
123 | ```console
124 | >> _dbmanager.get_primary_key(table="STUDENTS")
125 |
126 | "student_id"
127 | ```
128 |
129 | ### Add record to table
130 |
131 | ```console
132 | >> _dbmanager.add_record(table="STUDENTS", record={"student_id": "1010", "name":"ABC", "mark":10, "year":"2022"})
133 | ```
134 |
135 | ### Get all records from a table
136 |
137 | ```console
138 | >> _dbmanager.get_all_records(table="STUDENTS", primary_key="1010")
139 |
140 | [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}, {'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
141 | ```
142 |
143 | ### Get record from a table
144 |
145 | ```console
146 | >> _dbmanager.get_record(table="STUDENTS", primary_key="1010")
147 |
148 | {'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}
149 | ```
150 |
151 | ### Delete record from a table
152 |
153 | ```console
154 | >> _dbmanager.delete_record(table="STUDENTS", primary_key="1010")
155 | ```
156 |
157 | ### Filter record/s from a table
158 |
159 | If you want to filter **equal values**, add value without any operator.
160 |
161 | Examples:
162 |
163 | - `{"year":2022}` ✔️
164 | - `{"year":" == 2022"}` ❌
165 |
166 | 🖇 Comparison operators
167 |
168 | | Comparison Operator | Description |
169 | | :-----------------: | :-------------------: |
170 | | != | Not Equal |
171 | | > | Greater Than |
172 | | >= | Greater Than or Equal |
173 | | < | Less Than |
174 | | <= | Less Than or Equal |
175 |
176 | Examples:
177 |
178 | - `{"marks":"<= 10"}` ✔️
179 | - `{"marks":"== 10"}` ❌
180 | - `{"name":"< ABC"}` ❌ 'Greater Than' and 'Less than' comparisons are not supported with Strings
181 |
182 | **Important:** If you are trying to compare strings, please use string between Inch Marks.
183 |
184 | - `{"grade":"!= 'A'"}` ✔️
185 | - `{"grade":"!= A"}` ❌
186 |
187 | ```console
188 | >> _dbmanager.filter_records(table="STUDENTS", values={"year":"2022"})
189 |
190 | [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}, {'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
191 | ```
192 |
193 | ---
194 |
195 | ## 🌱 Contributing Guide
196 |
197 | - Fork the project from the `alpha` branch and submit a Pull Request (PR)
198 |
199 | - Explain what the PR fixes or improves.
200 |
201 | - If your PR aims to add a new feature, provide test functions as well.
202 |
203 | - Use sensible commit messages
204 |
205 | - If your PR fixes a separate issue number, include it in the commit message.
206 |
207 | - Use a sensible number of commit messages as well
208 |
209 | - e.g. Your PR should not have 1000s of commits.
210 |
211 | ### Run pytest without installing package
212 |
213 | If you are adding **new functions** as described above, please add test functions to `tests/test_manager.py`.
214 |
215 | ```console
216 | ~$ python -m pytest -s tests
217 | ```
218 |
--------------------------------------------------------------------------------
/tests/test_manager.py:
--------------------------------------------------------------------------------
1 | import os
2 | from sqlite3 import OperationalError
3 | from ReallySimpleDB import dbmanager
4 |
5 | _dbmanager = dbmanager()
6 |
7 | def test_create_db():
8 | """Create new database."""
9 | assert _dbmanager.create_db(dbpath="test.db", replace=True)
10 | delete_db()
11 |
12 | def test_create_table_1():
13 | """Create new database and new table with columns."""
14 | _dbmanager.create_db(dbpath="test.db", replace=True)
15 | _dbmanager.close_connection()
16 |
17 | _dbmanager.add_columns(column_name="student_id", primary_key=True)
18 | _dbmanager.add_columns(column_name="name", not_null=True)
19 | _dbmanager.add_columns(column_name="mark", datatype="INT")
20 | assert _dbmanager.create_table(database="test.db", table_name="STUDENTS")
21 |
22 | def test_create_table_2():
23 | """Create new table with columns."""
24 | _dbmanager.clean()
25 | _dbmanager.add_columns(column_name="teacher_id", primary_key=True)
26 | _dbmanager.add_columns(column_name="name", not_null=True)
27 | _dbmanager.add_columns(column_name="number", datatype="INT")
28 | assert _dbmanager.create_table(database="test.db", table_name="TEACHERS")
29 |
30 | def test_create_table_3():
31 | """Create new table with columns."""
32 | _dbmanager.clean()
33 | _dbmanager.add_columns(column_name="emp_id", primary_key=True)
34 | _dbmanager.add_columns(column_name="name", not_null=True)
35 | _dbmanager.add_columns(column_name="number", datatype="INT")
36 | assert _dbmanager.create_table(table_name="EMPLOYEES")
37 |
38 | def test_add_columns():
39 | """Add new column to a table."""
40 | assert _dbmanager.add_columns(column_name="year", database="test.db", table="STUDENTS")
41 |
42 | def test_all_tables_1():
43 | """If table exists in the database."""
44 | assert "STUDENTS" in _dbmanager.all_tables("test.db")
45 |
46 | def test_all_tables_2():
47 | """If table not in the database."""
48 | assert "NON" not in _dbmanager.all_tables("test.db")
49 |
50 | def test_all_tables_3():
51 | """If table exists in the database.. without define the db."""
52 | assert "STUDENTS" in _dbmanager.all_tables()
53 |
54 | def test_all_tables_4():
55 | """If table not in the database.. without define the db."""
56 | assert "NON" not in _dbmanager.all_tables()
57 |
58 | def test_is_table_1():
59 | """Check if the given table is exists in the database."""
60 | assert _dbmanager.is_table(database="test.db", table_name="STUDENTS")
61 |
62 | def test_is_table_2():
63 | """Check if the given table is not in the database."""
64 | assert not _dbmanager.is_table(database="test.db", table_name="NON")
65 |
66 | def test_is_table_3():
67 | """Check if the given table is exists in the database.. without define the db."""
68 | assert _dbmanager.is_table(table_name="STUDENTS")
69 |
70 | def test_is_table_4():
71 | """Check if the given table is not in the database.. without define the db."""
72 | assert not _dbmanager.is_table(table_name="NON")
73 |
74 | def test_delete_table_1():
75 | """Delete table if table exists."""
76 | try:
77 | _dbmanager.delete_table(table="EMPLOYEES")
78 | assert True
79 | except OperationalError:
80 | assert False
81 |
82 | def test_delete_table_2():
83 | """Delete table if table not exists."""
84 | try:
85 | _dbmanager.delete_table(table="EMPLOYEES")
86 | assert False
87 | except OperationalError:
88 | assert True
89 |
90 | def test_get_all_column_types_1():
91 | """Get all the column names with the data types in a table."""
92 | assert _dbmanager.get_all_column_types(table="STUDENTS") \
93 | == {"student_id": "TEXT", "name": "TEXT", "mark": "INT", "year": "TEXT"}
94 |
95 | def test_get_column_type_1():
96 | """Get data type of a column in a table."""
97 | assert _dbmanager.get_column_type(table="STUDENTS", column="student_id") == "TEXT"
98 |
99 | def test_get_column_type_2():
100 | """Get data type of not exists column in a table."""
101 | try:
102 | _dbmanager.get_column_type(table="STUDENTS", column="address")
103 | assert False
104 | except OperationalError:
105 | assert True
106 |
107 | def test_get_columns_1():
108 | """Get all the column names list in a table."""
109 | assert _dbmanager.get_columns(table="STUDENTS") == ["student_id", "name", "mark", "year"]
110 |
111 | def test_get_primary_key_1():
112 | """Find and get primary key of a table."""
113 | assert _dbmanager.get_primary_key(table="STUDENTS") == "student_id"
114 |
115 | def test_get_primary_key_2():
116 | """Find and get primary key of a table."""
117 | assert _dbmanager.get_primary_key(table="TEACHERS") == "teacher_id"
118 |
119 | def test_add_record_1():
120 | """Add a new record to a table."""
121 | assert _dbmanager.add_record(table="STUDENTS", record={"student_id": "1010", "name":"ABC", "mark":10, "year":"2022"}) == True
122 |
123 | def test_add_record_2():
124 | """Add a new record to a table."""
125 | assert _dbmanager.add_record(table="STUDENTS", record={"student_id": "1011", "name":"DEF", "mark":100, "year":"2022"}) == True
126 |
127 | def test_add_record_3():
128 | """Add a new record to a table with datatype errors."""
129 | try:
130 | _dbmanager.add_record(table="STUDENTS", record={"student_id": 10, "name":"ABC", "mark":10, "year":"2022"})
131 | assert False
132 | except TypeError:
133 | assert True
134 |
135 | def test_get_record_1():
136 | """Get row data / record from a table using the primary key."""
137 | assert _dbmanager.get_record(table="STUDENTS", primary_key="1010") == {'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}
138 |
139 | def test_get_record_2():
140 | """Get row data / record from a table using the primary key."""
141 | assert _dbmanager.get_record(table="STUDENTS", primary_key="10101") == {}
142 |
143 | def test_get_all_records():
144 | """Get all data / records of a table."""
145 | assert _dbmanager.get_all_records(table="STUDENTS") == [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'},
146 | {'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
147 |
148 | def test_filter_record_1():
149 | """Get filtered record list from a table."""
150 | assert _dbmanager.filter_records(table="STUDENTS", values={"year":"2022"}) == [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'},
151 | {'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
152 |
153 | def test_filter_record_2():
154 | """Get filtered record list from a table."""
155 | assert _dbmanager.filter_records(table="STUDENTS", values={"mark":100, "year":"2022"}) == [{'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
156 |
157 | def test_filter_record_3():
158 | """Get filtered record list from a table: Comparison."""
159 | assert _dbmanager.filter_records(table="STUDENTS", values={"mark":" <= 100"}) == [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}, {'student_id': '1011', 'name': 'DEF', 'mark': 100, 'year': '2022'}]
160 |
161 | def test_filter_record_4():
162 | """Get filtered record list from a table: Comparison."""
163 | assert _dbmanager.filter_records(table="STUDENTS", values={"mark":" != 100"}) == [{'student_id': '1010', 'name': 'ABC', 'mark': 10, 'year': '2022'}]
164 |
165 | def test_delete_record_1():
166 | """Delete record from a table."""
167 | assert _dbmanager.delete_record(table="STUDENTS", primary_key="1010")
168 |
169 | def test_delete_record_2():
170 | """Delete record from a table when table is not exists."""
171 | try:
172 | _dbmanager.delete_record(table="STUDENTSS", primary_key="1010")
173 | assert False
174 | except OperationalError:
175 | assert True
176 |
177 | def test_finally():
178 | """Delete the database."""
179 | delete_db()
180 |
181 | def delete_db(database="test.db"):
182 | """Close connection and deletes the database."""
183 | _dbmanager.close_connection()
184 | os.remove(database)
185 |
--------------------------------------------------------------------------------
/ReallySimpleDB/manager.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sqlite3
3 |
4 | from .utils import DATA_TYPES
5 |
6 | class ReallySimpleDB:
7 | """
8 | ReallySimpleDB class.
9 |
10 | ReallySimpleDB objects are the ones responsible of creating DBs, connecting
11 | with them, creating tables, adding records, geting records, among tasks. In
12 | more cases these should be one per database.
13 | """
14 |
15 | def __init__(self) -> None:
16 | """Create a object."""
17 | self._add_columns_cmd = ""
18 | self.connection = ""
19 |
20 | def clean(self):
21 | """
22 | Clean add_columns data.
23 |
24 | Why? _add_columns_cmd variable is for define SQL command. when using add_column,
25 | it sets up a string here. but when it is finished this is not clean and the data
26 | continues to exist. when use add_column again and again, it will be processed
27 | along with the existing data. this should be used to prevent it.
28 | """
29 | self._add_columns_cmd = ""
30 |
31 | def create_connection(self, database):
32 | """Open a connection to the SQLite database file."""
33 | self.connection = sqlite3.connect(database)
34 | return True
35 |
36 | def create_db(self, dbpath:str="", replace:bool=False):
37 | """Create a new database in a given path."""
38 | if self.connection == "" and not dbpath:
39 | raise TypeError("create_db() missing 1 required positional argument: 'dbpath'")
40 |
41 | if replace:
42 | # delete if database exists in given path
43 | if os.path.isfile(os.path.realpath(dbpath)):
44 | os.remove(os.path.realpath(dbpath))
45 |
46 | if not os.path.isfile(os.path.realpath(dbpath)):
47 | # create new connection with creating new database
48 | self.connection = sqlite3.connect(os.path.realpath(dbpath))
49 | return True
50 |
51 | raise FileExistsError(
52 | "'{}' file exists. for replace add parameter 'replace=True'".format(dbpath)
53 | )
54 |
55 | def add_columns(self,
56 | column_name:str,
57 | datatype:str="TEXT",
58 | primary_key:bool=False,
59 | not_null:bool=False,
60 | database:str="",
61 | table:str=""):
62 | """
63 | Add columns to an existing table / define columns before creating a table.
64 |
65 | If use for create new table: sqlite cannot create table without columns.
66 | so user must first define the columns and create a table.
67 | important: user have to close connection here. if not, code returns error.
68 | because it tries to add column to existing table.
69 | """
70 | # checks if the user is trying to add unsupported data type
71 | if datatype.upper() not in DATA_TYPES:
72 | raise TypeError("datatype not supported, '{}'".format(datatype))
73 |
74 | if database != "":
75 | if table == "":
76 | raise TypeError("add_columns() missing 1 required positional argument: 'table'")
77 |
78 | # if the table is defined, it means that the user is trying to add a
79 | # column to an existing table.
80 | self.create_connection(database=database)
81 | cursor = self.connection.cursor()
82 | sql_cmd = "ALTER TABLE " + table + " ADD COLUMN " + column_name + " " + datatype
83 | if not_null:
84 | sql_cmd += " NOT NULL"
85 | if primary_key:
86 | sql_cmd += " PRIMARY KEY"
87 | cursor.execute(sql_cmd)
88 | return True
89 |
90 | # if table is not defines, it means that the user is trying to add / define
91 | # a column to a new table. so the following code add SQL syntax globally for
92 | # use when creating new table
93 | self._add_columns_cmd += "," + column_name + " " + datatype
94 |
95 | if primary_key:
96 | self._add_columns_cmd += " PRIMARY KEY"
97 |
98 | if not_null:
99 | self._add_columns_cmd += " NOT NULL"
100 |
101 | return True
102 |
103 | def create_table(self, table_name:str, database:str=""):
104 | """Create new table in database."""
105 | if self.connection == "" and not database:
106 | raise TypeError("create_table() missing 1 required positional argument: 'database'")
107 |
108 | if database:
109 | self.create_connection(database)
110 |
111 | # if use for create new table: sqlite cannot create table without columns.
112 | # so user must first define the columns and create a table. using add_columns
113 | # can define columns for new table.
114 | if self._add_columns_cmd == "":
115 | raise NotImplementedError("call 'add_columns' function before create table")
116 |
117 | sql_cmd = "CREATE TABLE " + table_name + " (" + self._add_columns_cmd[1:] + ")"
118 |
119 | self.connection.execute(sql_cmd)
120 | return True
121 |
122 | def all_tables(self, database:str=""):
123 | """Get a list of all the tables in the database."""
124 | if self.connection == "" and not database:
125 | raise TypeError("all_tables() missing 1 required positional argument: 'database'")
126 |
127 | if database:
128 | self.create_connection(database)
129 |
130 | cursor = self.connection.cursor()
131 | sql_cmd = "SELECT name FROM sqlite_master WHERE type='table';"
132 | return [tables[0] for tables in cursor.execute(sql_cmd)]
133 |
134 | def is_table(self, table_name:str, database:str=""):
135 | """Check if the given table is exists in the database."""
136 | if self.connection == "" and not database:
137 | raise TypeError("is_table() missing 1 required positional argument: 'database'")
138 |
139 | if database:
140 | self.create_connection(database)
141 |
142 | if table_name in self.all_tables(database):
143 | return True
144 | return False
145 |
146 | def delete_table(self, table:str, database:str=""):
147 | """Delete a table from the database."""
148 | if self.connection == "" and not database:
149 | raise TypeError("delete_table() missing 1 required positional argument: 'database'")
150 |
151 | if database:
152 | self.create_connection(database)
153 |
154 | if self.is_table(table_name=table):
155 | cursor = self.connection.cursor()
156 | sql_cmd = "DROP TABLE " + table + ";"
157 | cursor.execute(sql_cmd)
158 |
159 | return True
160 |
161 | # raise OperationalError if the given table not exists
162 | raise sqlite3.OperationalError("no such table: {}".format(table))
163 |
164 | def get_all_column_types(self, table:str, database:str=""):
165 | """Get all the column names with the data types in a table."""
166 | if self.connection == "" and not database:
167 | raise TypeError(
168 | "get_all_column_types() missing 1 required positional argument: 'database'")
169 |
170 | if database:
171 | self.create_connection(database)
172 |
173 | if self.is_table(table_name=table, database=database):
174 | cursor = self.connection.cursor()
175 |
176 | sql_cmd = "PRAGMA TABLE_INFO(" + table + ");"
177 | fetch = cursor.execute(sql_cmd)
178 |
179 | data_dict = {}
180 | for data in fetch.fetchall():
181 | data_dict[data[1]] = data[2]
182 |
183 | return data_dict
184 |
185 | # raise OperationalError if the given table not exists
186 | raise sqlite3.OperationalError("no such table: {}".format(table))
187 |
188 | def get_column_type(self, table:str, column:str, database:str=""):
189 | """Get data type of a column in a table."""
190 | all_data = self.get_all_column_types(table=table, database=database)
191 |
192 | # if columns exists in the table and given column in the table
193 | if (not isinstance(all_data, bool)) and (column in all_data):
194 | return all_data[column]
195 |
196 | raise sqlite3.OperationalError("no such column: {}".format(column))
197 |
198 | def get_columns(self, table:str, database:str=""):
199 | """Get all the column names list in a table."""
200 | if self.connection == "" and not database:
201 | raise TypeError("get_columns() missing 1 required positional argument: 'database'")
202 |
203 | if database:
204 | self.create_connection(database)
205 |
206 | # get all columns with data types using get_all_column_types
207 | column_types = self.get_all_column_types(table=table, database=database)
208 | columns = []
209 | if isinstance(column_types, dict):
210 | for column in column_types:
211 | columns.append(column)
212 |
213 | return columns
214 |
215 | def get_primary_key(self, table:str, database:str=""):
216 | """Find and get primary key of a table."""
217 | if self.connection == "" and not database:
218 | raise TypeError("get_primary_key() missing 1 required positional argument: 'database'")
219 |
220 | if database:
221 | self.create_connection(database)
222 |
223 | if self.is_table(table_name=table, database=database):
224 | cursor = self.connection.cursor()
225 |
226 | sql_cmd = "SELECT * FROM pragma_table_info(?) WHERE pk;"
227 | fetch = cursor.execute(sql_cmd, (table,))
228 | return fetch.fetchall()[0][1]
229 |
230 | # raise OperationalError if the given table not exists
231 | raise sqlite3.OperationalError("no such table: {}".format(table))
232 |
233 | def add_record(self, table:str, record, database:str=""):
234 | """Add a new record to a table."""
235 | if self.connection == "" and not database:
236 | raise TypeError("add_record() missing 1 required positional argument: 'database'")
237 |
238 | if database:
239 | self.create_connection(database)
240 |
241 | if self.is_table(table_name=table, database=database):
242 | cursor = self.connection.cursor()
243 |
244 | # get all columns with data types using get_all_column_types
245 | tmp_all_columns = self.get_all_column_types(table=table, database=database)
246 | all_columns = {}
247 |
248 | # appends column names of the given table to all_columns dictionary
249 | for column in tmp_all_columns:
250 | all_columns[column] = ""
251 |
252 | fields = []
253 | sql_cmd = "INSERT INTO " + table + " VALUES("
254 |
255 | # if record is dict type,..
256 | if isinstance(record, dict):
257 | for field in record:
258 | # if the user has defined a column that is not in the table..
259 | if field not in all_columns:
260 | raise NameError("'{}' column is not in the table".format(field))
261 |
262 | # if the user has defines values that is match with the
263 | # datatypes of the columns..
264 | if DATA_TYPES[tmp_all_columns[field]] == type(record[field]):
265 | all_columns[field] = record[field]
266 | else:
267 | raise TypeError("The '{}' field requires '{}' but got '{}'"
268 | .format(field, DATA_TYPES[tmp_all_columns[field]], type(record[field])))
269 |
270 | # creates the full SQL command
271 | for field in all_columns:
272 | fields.append(all_columns[field])
273 | sql_cmd+= "?,"
274 |
275 | # removes unnecessary characters and complete the SQL command
276 | sql_cmd = sql_cmd[:-1] + ");"
277 |
278 | cursor.execute(sql_cmd, fields)
279 | self.connection.commit()
280 | else:
281 | raise TypeError("'record' must be dict")
282 |
283 | return True
284 |
285 | # raise OperationalError if the given table not exists
286 | raise sqlite3.OperationalError("no such table: {}".format(table))
287 |
288 | def get_record(self, table:str, primary_key, database:str=""):
289 | """Get row data / record from a table using the primary key."""
290 | if self.connection == "" and not database:
291 | raise TypeError("get_record() missing 1 required positional argument: 'database'")
292 |
293 | if database:
294 | self.create_connection(database)
295 |
296 | if self.is_table(table_name=table, database=database):
297 | cursor = self.connection.cursor()
298 |
299 | sql_cmd = "SELECT * FROM " + table + " WHERE " + self.get_primary_key(table=table, database=database) + "=?;"
300 | fetch = cursor.execute(sql_cmd, (primary_key,))
301 |
302 | # get columns list using get_columns
303 | columns = self.get_columns(table=table, database=database)
304 | record = {}
305 |
306 | try:
307 | for index, data in enumerate(fetch.fetchall()[0]):
308 | # this creates dictionary with column names and records
309 | record[columns[index]] = data
310 | except IndexError:
311 | # if the table does not have the requested data it returns
312 | # a empty list. so above for loop will raise an IndexError.
313 | return {}
314 |
315 | return record
316 |
317 | # raise OperationalError if the given table not exists
318 | raise sqlite3.OperationalError("no such table: {}".format(table))
319 |
320 | def get_all_records(self, table:str, database:str=""):
321 | """Get all data / records of a table."""
322 | if self.connection == "" and not database:
323 | raise TypeError("get_all_records() missing 1 required positional argument: 'database'")
324 |
325 | if database:
326 | self.create_connection(database)
327 |
328 | if self.is_table(table_name=table, database=database):
329 | cursor = self.connection.cursor()
330 |
331 | sql_cmd = "SELECT * FROM " + table
332 | cursor.execute(sql_cmd)
333 | rows = cursor.fetchall()
334 |
335 | # get columns list using get_columns
336 | columns = self.get_columns(table=table, database=database)
337 | records = []
338 | tmp_dict = {}
339 |
340 | for row in rows:
341 | for index, data in enumerate(row):
342 | # this creates dictionary with column names and records
343 | tmp_dict[columns[index]] = data
344 | records.append(tmp_dict)
345 | tmp_dict = {}
346 |
347 | return records
348 |
349 | # raise OperationalError if the given table not exists
350 | raise sqlite3.OperationalError("no such table: {}".format(table))
351 |
352 | def delete_record(self, table:str, primary_key, database:str=""):
353 | """Delete record from a table."""
354 | if self.connection == "" and not database:
355 | raise TypeError("delete_record() missing 1 required positional argument: 'database'")
356 |
357 | if database:
358 | self.create_connection(database)
359 |
360 | if self.is_table(table_name=table, database=database):
361 | cursor = self.connection.cursor()
362 | sql = "DELETE FROM " + table + " WHERE " + self.get_primary_key(table=table, database=database) + "=?"
363 | cursor.execute(sql, (primary_key,))
364 | self.connection.commit()
365 |
366 | return True
367 |
368 | # raise OperationalError if the given table not exists
369 | raise sqlite3.OperationalError("no such table: {}".format(table))
370 |
371 | def filter_records(self, table:str, values:dict, database:str=""):
372 | """
373 | Get filtered record list from a table.
374 |
375 | This will return one or more records by checking the values.
376 | """
377 | if self.connection == "" and not database:
378 | raise TypeError("filter_records() missing 1 required positional argument: 'database'")
379 |
380 | if database:
381 | self.create_connection(database)
382 |
383 | if self.is_table(table_name=table, database=database):
384 | cursor = self.connection.cursor()
385 |
386 | operators = [">", "<", "!", "="]
387 |
388 | sql = "SELECT * FROM " + table + " WHERE "
389 |
390 | for value in values:
391 | try:
392 | # if value is in string type
393 | # checks for if value contains any special character
394 | if any(c in operators for c in values[value]):
395 | sql += value + values[value] + " AND "
396 | else:
397 | sql += value + "='" + values[value] + "' AND "
398 |
399 | except TypeError:
400 | # if value is in int or float type
401 | sql += value + "=" + str(values[value]) + " AND "
402 |
403 | # removes unnecessary characters and completes the SQL command
404 | sql = sql[:-5] + ";"
405 |
406 | cursor.execute(sql)
407 | rows = cursor.fetchall()
408 |
409 | columns = self.get_columns(table=table, database=database)
410 | records = []
411 | tmp_dict = {}
412 |
413 | for row in rows:
414 | for index, data in enumerate(row):
415 | # this creates dictionary with column names and records
416 | tmp_dict[columns[index]] = data
417 | records.append(tmp_dict)
418 | tmp_dict = {}
419 |
420 | return records
421 |
422 | # raise OperationalError if the given table not exists
423 | raise sqlite3.OperationalError("no such table: {}".format(table))
424 |
425 | def close_connection(self):
426 | """Close the connection with the SQLite database file."""
427 | self.connection.close()
428 | return True
429 |
--------------------------------------------------------------------------------