├── 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 |

2 | ReallySimpleDB 🧩 3 |

4 | 5 | Icon 6 | 7 | [![tests](https://github.com/truethari/ReallySimpleDB/actions/workflows/tests.yml/badge.svg?branch=alpha)](https://github.com/truethari/ReallySimpleDB/actions/workflows/tests.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/befe3049923e4e788f5a1d6d958f6015)](https://www.codacy.com/gh/truethari/ReallySimpleDB/dashboard?utm_source=github.com&utm_medium=referral&utm_content=truethari/ReallySimpleDB&utm_campaign=Badge_Grade) [![PyPI version](https://img.shields.io/pypi/v/ReallySimpleDB.svg?logo=pypi&logoColor=FFE873)](https://pypi.org/project/ReallySimpleDB/) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![Downloads](https://pepy.tech/badge/reallysimpledb)](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 | --------------------------------------------------------------------------------