Skip to content

Commit c1e5bd2

Browse files
Upload final project
1 parent b6e83f5 commit c1e5bd2

File tree

19 files changed

+1601
-1
lines changed

19 files changed

+1601
-1
lines changed

Projects/TableTracker

Lines changed: 0 additions & 1 deletion
This file was deleted.

Projects/table_tracker/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<h1 align="center"> TableTracker</h1>
2+
3+
<p align="center">
4+
<img src="https://github.com/Musa-Sina-Ertugrul/DBScanner/assets/102359522/1ea4b501-898f-4b57-8a7e-90e853d50cdd">
5+
</p>
6+
7+
<h3 align="center"> Our Purpose</h3>
8+
9+
<p align="center">
10+
<img src="https://github.com/Musa-Sina-Ertugrul/TableTracker/assets/102359522/cd2e98f0-083e-44f4-a532-e4e9b9a29c47">
11+
</p>
12+
13+
14+
# TableTracker
15+
16+
> TableTracker is a desktop application developed in Python that facilitates tracking and managing SQLite database tables. This application allows you to execute SQL queries on SQLite databases, visualize the results, and edit your queries.
17+
* Assigned by Asc. Prof. Dr. Bora CANBULA
18+
19+
<h3 align="center">Requirements</h3>
20+
> Python 3.11 or a newer version
21+
22+
<h3 align="center">Setup</h3>
23+
24+
> To create the necessary virtual environment in the project directory, run the following command:
25+
26+
```console
27+
conda create -n TableTracker python=3.11 pip -y
28+
```
29+
30+
> Activate the created virtual environment:
31+
32+
```console
33+
conda activate TableTracker
34+
```
35+
36+
> To install required modules
37+
38+
```console
39+
pip install -r requirements.txt
40+
```
41+
42+
<h3 align="center">Reformatting</h3>
43+
44+
> For reformatting use <b><i>black</i></b>. It reformat for pep8 as same as pylint but better !!!
45+
46+
```console
47+
black .
48+
```
49+
50+
<h3 align="center">Run App</h3>
51+
52+
> To start the application, run the following command:
53+
54+
```console
55+
python table_tracker
56+
```
57+
58+
> .When the application starts, you can create a new SQLite database or connect to an existing one.
59+
> Write your SQL queries in the text box and execute the query by clicking the "Execute" button.
60+
> The results will be displayed in the "Output Window" section.
61+
62+
<h3 align="center">Linting</h3>
63+
64+
> For running <b><i>pylint</i></b>
65+
66+
```console
67+
pylint ./table_tracker/ ./test/
68+
```
69+
70+
<h3 align="center">Testing</h3>
71+
72+
> For running <b><i>unittest</i></b>
73+
74+
```console
75+
python -m unittest discover -v
76+
```
77+
<h3 align="center">Author</h3>
78+
> Musa Sina Ertuğrul, İrem Demir

Projects/table_tracker/__main__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import sys
2+
from packages import App
3+
4+
sys.path.append("./tmp_files/")
5+
6+
if __name__ == "__main__":
7+
app = App()
8+
app.mainloop()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .gui import App
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .sql_event_handler import SQLEventHandler
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from abc import ABCMeta
2+
from typing import Literal
3+
from types import NotImplementedType
4+
from packages.utils import check_methods
5+
6+
7+
class EventHandler(metaclass=ABCMeta):
8+
"""
9+
Abstract base class for event handling.
10+
11+
EventHandler class defines the interface for event handling. Subclasses must implement the handle method.
12+
"""
13+
14+
__slots__: tuple = ()
15+
16+
@classmethod
17+
def __subclasshook__(cls, subcls) -> NotImplementedType | Literal[True]:
18+
"""
19+
Check if a subclass implements the required methods.
20+
21+
:param subcls: The potential subclass.
22+
:return: NotImplementedType if the method is not implemented, True otherwise.
23+
:rtype: NotImplementedType | Literal[True]
24+
"""
25+
return check_methods(subcls, "handle")
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import sqlite3
2+
from typing import Self
3+
from functools import cache, reduce
4+
from sql_formatter.core import format_sql
5+
from sys import getsizeof
6+
import math
7+
from psutil import virtual_memory
8+
import customtkinter
9+
from .event_handler import EventHandler
10+
from ..utils import QUERY_ERROR_NONE_OBJECT
11+
12+
13+
class SQLEventHandler(EventHandler):
14+
"""Handler for SQL events."""
15+
16+
MAX_PAGE_COUNT: int = 10
17+
queries: list[str] = []
18+
query_index: int = 0
19+
20+
@cache
21+
def __new__(cls, *args, **kwargs) -> Self:
22+
"""
23+
Create a new instance of SQLEventHandler class.
24+
Implements the Singleton pattern.
25+
"""
26+
return super().__new__(cls)
27+
28+
def __init__(
29+
self, query: str, cursor: sqlite3.Cursor, result_label: customtkinter.CTkLabel
30+
) -> None:
31+
"""
32+
Initialize the SQLEventHandler instance.
33+
If the singleton query has not been created, it initializes the attributes accordingly.
34+
35+
:param query: The SQL query.
36+
:type query: str
37+
:param cursor: The cursor for executing the query.
38+
:type cursor: sqlite3.Cursor
39+
:param result_label: The label to display the result.
40+
:type result_label: customtkinter.CTkLabel
41+
"""
42+
if not hasattr(self, "_query"):
43+
self._query: str = query
44+
self._cursor: sqlite3.Cursor = cursor
45+
self._result_label: customtkinter.CTkLabel = result_label
46+
self.__col_len: int = 0
47+
self.__row_len: int = 0
48+
self.__total_size: int = 0
49+
self.queries.append(format_sql(query, max_len=1000000000))
50+
self.query_index += 1
51+
52+
@property
53+
def get_query(self) -> str:
54+
"""
55+
Get the SQL query.
56+
57+
:return: The SQL query. otherwise None.
58+
:rtype: str| None
59+
"""
60+
if not sqlite3.complete_statement(self._query):
61+
return QUERY_ERROR_NONE_OBJECT
62+
return self._query
63+
64+
@property
65+
def get_query_result_itr(self) -> sqlite3.Cursor | None:
66+
"""
67+
68+
Get the iterator for executing the SQL query.
69+
70+
:return: Iterator for executing the SQL query. Returns None if there is an error.
71+
:rtype: sqlite3.Cursor or None
72+
:raise KeyError: Raised in a specific error condition.
73+
"""
74+
try:
75+
return self._cursor.execute(self.get_query)
76+
except (sqlite3.ProgrammingError, AttributeError) as error:
77+
print(error)
78+
return QUERY_ERROR_NONE_OBJECT
79+
80+
@staticmethod
81+
def _sizeof_row(row: list[tuple]) -> int:
82+
"""
83+
Calculate the size of a row in bytes.
84+
85+
:param row: List of tuples representing a row.
86+
:type row: list[tuple]
87+
:return: Size of the row in bytes.
88+
:rtype: int
89+
"""
90+
return reduce(getsizeof, [str(atr) for atr in row])
91+
92+
@property
93+
def row_len(self) -> int:
94+
"""
95+
Get the length of the rows.
96+
97+
If the row length attribute is not set, it returns the length calculated from the result set.
98+
99+
:return: Length of the rows.
100+
:rtype: int
101+
"""
102+
return self.__row_len or len(self)
103+
104+
@cache
105+
def __len__(self) -> int:
106+
"""
107+
Get the total number of rows in the result set.
108+
109+
This method iterates through the result set obtained from the query execution and counts the rows. It also
110+
calculates the total size of the result set.
111+
112+
:return: Total number of rows in the result set.
113+
:rtype: int
114+
:raise KeyError: Raised in a specific error condition.
115+
"""
116+
try:
117+
row_itr: sqlite3.Cursor = self.get_query_result_itr
118+
row: list[tuple] = next(row_itr)
119+
self.__col_len = len(row)
120+
self.__total_size += self._sizeof_row(row)
121+
for row_count, row in enumerate(row_itr):
122+
self.__total_size += self._sizeof_row(row)
123+
self.__row_len = row_count + 1
124+
return self.__row_len
125+
except (sqlite3.ProgrammingError, StopIteration, TypeError) as error:
126+
return 0
127+
128+
@property
129+
@cache
130+
def col_len(self) -> int:
131+
"""
132+
Get the total number of columns in the result set.
133+
134+
This method returns the number of columns in the result set.
135+
136+
:return: Total number of columns in the result set.
137+
:rtype: int
138+
139+
"""
140+
return self.__col_len
141+
142+
@property
143+
def divaded_itrs(self) -> tuple[sqlite3.Cursor]:
144+
"""
145+
Divide the result set into multiple cursors.
146+
147+
This method divides the result set into multiple cursors based on the available memory and the size of the result set.
148+
It calculates the number of pages and rows per page, then creates cursors accordingly.
149+
150+
:return: Tuple of cursors representing the divided result set.
151+
:rtype: tuple[sqlite3.Cursor]
152+
:raise KeyError: Raised in a specific error condition.
153+
"""
154+
len(self)
155+
avaible_memory: int = int(virtual_memory()[1])
156+
157+
page_count: int = max(
158+
min(
159+
math.ceil(self.__total_size // (avaible_memory / 4.0)),
160+
self.MAX_PAGE_COUNT,
161+
),
162+
1,
163+
)
164+
row_count: int = self.__total_size // page_count
165+
166+
try:
167+
itrs: sqlite3.Cursor = [
168+
self.get_query_result_itr for _ in range(page_count)
169+
]
170+
except TypeError:
171+
return QUERY_ERROR_NONE_OBJECT
172+
173+
try:
174+
for current_page, itr in enumerate(itrs[1:], 1):
175+
for _ in range(current_page * row_count):
176+
next(itr)
177+
except IndexError:
178+
return itrs
179+
180+
return itrs
181+
182+
def handle(self) -> tuple[sqlite3.Cursor]:
183+
"""
184+
Handle the SQL query result.
185+
186+
This method retrieves the divided iterators of the SQL query result using the `divaded_itrs` method and returns them.
187+
188+
:return: Tuple of cursors representing the divided result set.
189+
:rtype: tuple
190+
191+
"""
192+
return self.divaded_itrs
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .syntax_error import SytanxErrorHandler
2+
from .text_coloring import TextColoringHandler
3+
from .format_text import FormatTextHandler

0 commit comments

Comments
 (0)