ccfx 1.0.6__tar.gz → 1.0.8__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.
- {ccfx-1.0.6/ccfx.egg-info → ccfx-1.0.8}/PKG-INFO +1 -1
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/ccfx.py +72 -45
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/sqliteConnection.py +120 -23
- {ccfx-1.0.6 → ccfx-1.0.8/ccfx.egg-info}/PKG-INFO +1 -1
- {ccfx-1.0.6 → ccfx-1.0.8}/pyproject.toml +1 -1
- {ccfx-1.0.6 → ccfx-1.0.8}/LICENSE +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/MANIFEST.in +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/README.md +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/__init__.py +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/excel.py +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/mssqlConnection.py +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx/word.py +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx.egg-info/SOURCES.txt +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx.egg-info/dependency_links.txt +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx.egg-info/requires.txt +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/ccfx.egg-info/top_level.txt +0 -0
- {ccfx-1.0.6 → ccfx-1.0.8}/setup.cfg +0 -0
@@ -35,7 +35,7 @@ import math
|
|
35
35
|
import requests
|
36
36
|
from tqdm import tqdm
|
37
37
|
import yt_dlp
|
38
|
-
from typing import Optional
|
38
|
+
from typing import Optional, Any
|
39
39
|
from datetime import datetime, timedelta
|
40
40
|
from PIL import Image
|
41
41
|
|
@@ -72,7 +72,7 @@ def getExtension(filePath:str) -> str:
|
|
72
72
|
return os.path.splitext(filePath)[1].lstrip('.')
|
73
73
|
|
74
74
|
|
75
|
-
def getMp3Metadata(fn, imagePath=None):
|
75
|
+
def getMp3Metadata(fn: str, imagePath: Optional[str] = None) -> dict:
|
76
76
|
'''
|
77
77
|
This function takes a path to mp3 and returns a dictionary with
|
78
78
|
the following keys:
|
@@ -127,7 +127,7 @@ def getMp3Metadata(fn, imagePath=None):
|
|
127
127
|
return metadata
|
128
128
|
|
129
129
|
|
130
|
-
def guessMimeType(imagePath):
|
130
|
+
def guessMimeType(imagePath: str) -> str:
|
131
131
|
ext = os.path.splitext(imagePath.lower())[1]
|
132
132
|
if ext in ['.jpg', '.jpeg']:
|
133
133
|
return 'image/jpeg'
|
@@ -240,7 +240,7 @@ def parseYoutubeChannelVideos(channelUrl: str, maxItems: Optional[int] = None) -
|
|
240
240
|
return [f"https://www.youtube.com/watch?v={e['id']}" for e in entries if e.get("id")]
|
241
241
|
|
242
242
|
|
243
|
-
def runSWATPlus(txtinoutDir: str, finalDir: str, executablePath: str = "swatplus", v: bool = True):
|
243
|
+
def runSWATPlus(txtinoutDir: str, finalDir: str, executablePath: str = "swatplus", v: bool = True) -> None:
|
244
244
|
os.chdir(txtinoutDir)
|
245
245
|
|
246
246
|
if not v:
|
@@ -339,7 +339,7 @@ def formatTimedelta(delta: timedelta) -> str:
|
|
339
339
|
return f"{time_fmt}"
|
340
340
|
|
341
341
|
|
342
|
-
def setMp3Metadata(fn, metadata, imagePath=None):
|
342
|
+
def setMp3Metadata(fn: str, metadata: dict, imagePath: Optional[str] = None) -> bool:
|
343
343
|
'''
|
344
344
|
This function takes a path to an mp3 and a metadata dictionary,
|
345
345
|
then writes that metadata to the file's ID3 tags.
|
@@ -434,7 +434,7 @@ def deleteFile(filePath:str, v:bool = False) -> bool:
|
|
434
434
|
|
435
435
|
return deleted
|
436
436
|
|
437
|
-
def removeImageColour(inPath:str, outPath:str, colour:tuple = (255, 255, 255), tolerance:int = 30):
|
437
|
+
def removeImageColour(inPath:str, outPath:str, colour:tuple = (255, 255, 255), tolerance:int = 30) -> None:
|
438
438
|
'''
|
439
439
|
Remove a specific color from an image.
|
440
440
|
colour: RGB tuple, e.g., (255, 0, 0) for red
|
@@ -457,7 +457,7 @@ def removeImageColour(inPath:str, outPath:str, colour:tuple = (255, 255, 255), t
|
|
457
457
|
img.putdata(new_data)
|
458
458
|
img.save(outPath)
|
459
459
|
|
460
|
-
def makeTransparent(inPath:str, outPath:str, colour:tuple = (255, 255, 255), tolerance:int = 30):
|
460
|
+
def makeTransparent(inPath:str, outPath:str, colour:tuple = (255, 255, 255), tolerance:int = 30) -> None:
|
461
461
|
'''
|
462
462
|
Make some pixels in an image transparent.
|
463
463
|
'''
|
@@ -519,7 +519,7 @@ def deletePath(path:str, v:bool = False) -> bool:
|
|
519
519
|
deleted = False
|
520
520
|
|
521
521
|
|
522
|
-
def downloadChunk(url, start, end, path):
|
522
|
+
def downloadChunk(url: str, start: int, end: int, path: str) -> None:
|
523
523
|
headers = {'Range': f'bytes={start}-{end}'}
|
524
524
|
response = requests.get(url, headers=headers, stream=True)
|
525
525
|
with open(path, 'wb') as f:
|
@@ -528,7 +528,7 @@ def downloadChunk(url, start, end, path):
|
|
528
528
|
f.write(chunk)
|
529
529
|
|
530
530
|
|
531
|
-
def formatStringBlock(input_str, max_chars=70):
|
531
|
+
def formatStringBlock(input_str: str, max_chars: int = 70) -> str:
|
532
532
|
'''
|
533
533
|
This function takes a string and formats it into a block of text
|
534
534
|
with a maximum number of characters per line.
|
@@ -559,7 +559,7 @@ def formatStringBlock(input_str, max_chars=70):
|
|
559
559
|
|
560
560
|
|
561
561
|
|
562
|
-
def downloadFile(url, save_path, exists_action='resume', num_connections=5, v=False):
|
562
|
+
def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_connections: int = 5, v: bool = False) -> None:
|
563
563
|
if v:
|
564
564
|
print(f"\ndownloading {url}")
|
565
565
|
fname = getFileBaseName(url, extension=True)
|
@@ -649,7 +649,7 @@ def systemPlatform() -> str:
|
|
649
649
|
'''
|
650
650
|
return platform.system()
|
651
651
|
|
652
|
-
def progressBar(count, total, message=""):
|
652
|
+
def progressBar(count: int, total: int, message: str = "") -> None:
|
653
653
|
percent = int(count / total * 100)
|
654
654
|
filled = int(percent / 2)
|
655
655
|
bar = '█' * filled + '░' * (50 - filled)
|
@@ -669,7 +669,7 @@ def fileCount(path:str = "./", extension:str = ".*", v:bool = True) -> int:
|
|
669
669
|
print(f'> there are {count} {extension if not extension ==".*" else ""} files in {path}')
|
670
670
|
return count
|
671
671
|
|
672
|
-
def resampleRaster(inFile:str, outFile:str, resolution:float, dstSRS = None, resamplingMethod = 'bilinear', replaceOutput:bool = True, v:bool = True) -> str:
|
672
|
+
def resampleRaster(inFile:str, outFile:str, resolution:float, dstSRS = None, resamplingMethod = 'bilinear', replaceOutput:bool = True, v:bool = True) -> Optional[str]:
|
673
673
|
'''
|
674
674
|
Resample a raster file
|
675
675
|
inFile: input raster file
|
@@ -740,7 +740,7 @@ def watchFileCount(path:str="./", extension:str = ".*", interval:float = 0.2, du
|
|
740
740
|
return None
|
741
741
|
|
742
742
|
|
743
|
-
def pythonVariable(filename, option, variable=None):
|
743
|
+
def pythonVariable(filename: str, option: str, variable: Any = None) -> Any:
|
744
744
|
'''
|
745
745
|
option: save, load or open
|
746
746
|
|
@@ -774,7 +774,7 @@ def listFolders(path:str) -> list:
|
|
774
774
|
else:
|
775
775
|
return []
|
776
776
|
|
777
|
-
def readFrom(filename, decode_codec = None, v=False):
|
777
|
+
def readFrom(filename: str, decode_codec: Optional[str] = None, v: bool = False) -> Any:
|
778
778
|
'''
|
779
779
|
a function to read ascii files
|
780
780
|
'''
|
@@ -793,16 +793,16 @@ def readFrom(filename, decode_codec = None, v=False):
|
|
793
793
|
|
794
794
|
|
795
795
|
def pointsToGeodataframe(
|
796
|
-
rowList,
|
797
|
-
columnNames,
|
798
|
-
latIndex,
|
799
|
-
lonIndex,
|
800
|
-
auth = "EPSG",
|
801
|
-
code = "4326",
|
802
|
-
outShape = "",
|
803
|
-
format = "gpkg",
|
804
|
-
v = False,
|
805
|
-
includeLatLon = True ) -> geopandas.GeoDataFrame:
|
796
|
+
rowList: list,
|
797
|
+
columnNames: list,
|
798
|
+
latIndex: int,
|
799
|
+
lonIndex: int,
|
800
|
+
auth: str = "EPSG",
|
801
|
+
code: str = "4326",
|
802
|
+
outShape: str = "",
|
803
|
+
format: str = "gpkg",
|
804
|
+
v: bool = False,
|
805
|
+
includeLatLon: bool = True ) -> geopandas.GeoDataFrame:
|
806
806
|
df = pandas.DataFrame(rowList, columns = columnNames)
|
807
807
|
geometry = [
|
808
808
|
Point(row[lonIndex], row[latIndex]) for row in rowList
|
@@ -824,10 +824,10 @@ def pointsToGeodataframe(
|
|
824
824
|
return gdf
|
825
825
|
|
826
826
|
|
827
|
-
def readFile(filename, decode_codec = None, v=False):
|
827
|
+
def readFile(filename: str, decode_codec: Optional[str] = None, v: bool = False) -> Any:
|
828
828
|
return readFrom(filename, decode_codec, v)
|
829
829
|
|
830
|
-
def writeTo(filename, file_text, encode_codec = None, v=False) -> bool:
|
830
|
+
def writeTo(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool:
|
831
831
|
'''
|
832
832
|
a function to write ascii files
|
833
833
|
'''
|
@@ -846,13 +846,13 @@ def writeTo(filename, file_text, encode_codec = None, v=False) -> bool:
|
|
846
846
|
if v: print("\t> wrote {0}".format(getFileBaseName(filename, extension=True)))
|
847
847
|
return True
|
848
848
|
|
849
|
-
def writeToFile(filename, file_text, encode_codec = None, v=False) -> bool:
|
849
|
+
def writeToFile(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool:
|
850
850
|
return writeTo(filename, file_text, encode_codec, v)
|
851
851
|
|
852
|
-
def writeFile(filename, file_text, encode_codec = None, v=False) -> bool:
|
852
|
+
def writeFile(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool:
|
853
853
|
return writeTo(filename, file_text, encode_codec, v)
|
854
854
|
|
855
|
-
def createPath(pathName, v = False):
|
855
|
+
def createPath(pathName: str, v: bool = False) -> str:
|
856
856
|
'''
|
857
857
|
this function creates a directory if it does not exist
|
858
858
|
pathName: the path to create
|
@@ -871,7 +871,7 @@ def createPath(pathName, v = False):
|
|
871
871
|
return pathName
|
872
872
|
|
873
873
|
|
874
|
-
def renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, new_var_name: str, v = False) -> None:
|
874
|
+
def renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, new_var_name: str, v: bool = False) -> None:
|
875
875
|
"""
|
876
876
|
Renames a variable in a NetCDF file using CDO if it exists.
|
877
877
|
If the variable does not exist, the file is copied without modification.
|
@@ -907,7 +907,7 @@ def renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, n
|
|
907
907
|
print(f"Error: {e.stderr}")
|
908
908
|
|
909
909
|
|
910
|
-
def compressTo7z(input_dir: str, output_file: str, compressionLevel: int = 4, excludeExt: list = None, v: bool = False) -> None:
|
910
|
+
def compressTo7z(input_dir: str, output_file: str, compressionLevel: int = 4, excludeExt: Optional[list] = None, v: bool = False) -> None:
|
911
911
|
"""
|
912
912
|
Compresses the contents of a directory to a .7z archive with maximum compression.
|
913
913
|
|
@@ -1210,8 +1210,8 @@ def ignoreWarnings(ignore:bool = True, v:bool = False) -> None:
|
|
1210
1210
|
return None
|
1211
1211
|
|
1212
1212
|
|
1213
|
-
def createGrid(topLeft: list = None, bottomRight: list = None, resolution: float = None,
|
1214
|
-
inputShape: str = None, crs: str = "EPSG:4326", saveVector: str = None) -> geopandas.GeoDataFrame:
|
1213
|
+
def createGrid(topLeft: Optional[list] = None, bottomRight: Optional[list] = None, resolution: Optional[float] = None,
|
1214
|
+
inputShape: Optional[str] = None, crs: str = "EPSG:4326", saveVector: Optional[str] = None) -> geopandas.GeoDataFrame:
|
1215
1215
|
'''
|
1216
1216
|
This function creates a grid of polygons based on either a shapefile or corner coordinates
|
1217
1217
|
|
@@ -1341,7 +1341,7 @@ def netcdfVariableDimensions(ncFile: str, variable: str) -> dict:
|
|
1341
1341
|
|
1342
1342
|
return bands_info
|
1343
1343
|
|
1344
|
-
def netcdfExportTif(ncFile: str, variable: str, outputFile: Optional[str] = None, band: int = None, v:bool = True) -> gdal.Dataset:
|
1344
|
+
def netcdfExportTif(ncFile: str, variable: str, outputFile: Optional[str] = None, band: Optional[int] = None, v:bool = True) -> gdal.Dataset:
|
1345
1345
|
'''
|
1346
1346
|
Export a variable from a NetCDF file to a GeoTiff file
|
1347
1347
|
ncFile: NetCDF file
|
@@ -1401,8 +1401,8 @@ def netcdfSumMaps(ncFiles:list, variable:str, band:int = 1) -> numpy.ndarray:
|
|
1401
1401
|
def tiffWriteArray(array: numpy.ndarray, outputFile: str,
|
1402
1402
|
geoTransform: tuple = (0, 1, 0, 0, 0, -1),
|
1403
1403
|
projection: str = 'EPSG:4326',
|
1404
|
-
noData:float = None,
|
1405
|
-
v:bool = False) -> gdal.Dataset:
|
1404
|
+
noData: Optional[float] = None,
|
1405
|
+
v: bool = False) -> gdal.Dataset:
|
1406
1406
|
'''
|
1407
1407
|
Write a numpy array to a GeoTIFF file
|
1408
1408
|
array : numpy array to write
|
@@ -1448,7 +1448,7 @@ def copyFile(source:str, destination:str, v:bool = True) -> None:
|
|
1448
1448
|
if v: print(f'> {source} copied to \t - {destination}')
|
1449
1449
|
|
1450
1450
|
|
1451
|
-
def copyDirectory(source:str, destination:str, recursive = True, v:bool = True, filter = []) -> None:
|
1451
|
+
def copyDirectory(source:str, destination:str, recursive: bool = True, v:bool = True, filter: list = []) -> None:
|
1452
1452
|
'''
|
1453
1453
|
Copy a directory from source to destination
|
1454
1454
|
source: source directory
|
@@ -1499,7 +1499,7 @@ def copyFolder(source:str, destination:str, v:bool = True) -> None:
|
|
1499
1499
|
copyDirectory(source, destination, v=v)
|
1500
1500
|
|
1501
1501
|
|
1502
|
-
def convertCoordinates(lon, lat, srcEPSG, dstCRS) -> tuple:
|
1502
|
+
def convertCoordinates(lon: float, lat: float, srcEPSG: str, dstCRS: str) -> tuple:
|
1503
1503
|
"""
|
1504
1504
|
this function converts coordinates from one CRS to another
|
1505
1505
|
|
@@ -1516,7 +1516,7 @@ def convertCoordinates(lon, lat, srcEPSG, dstCRS) -> tuple:
|
|
1516
1516
|
return (new_lon, new_lat)
|
1517
1517
|
|
1518
1518
|
|
1519
|
-
def extractRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> float:
|
1519
|
+
def extractRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> Optional[float]:
|
1520
1520
|
"""
|
1521
1521
|
Extract raster value at given coordinates.
|
1522
1522
|
|
@@ -1561,7 +1561,7 @@ def extractRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str =
|
|
1561
1561
|
return float(value)
|
1562
1562
|
|
1563
1563
|
|
1564
|
-
def getRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> float:
|
1564
|
+
def getRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> Optional[float]:
|
1565
1565
|
'''
|
1566
1566
|
this function is a wrapper for extractRasterValue
|
1567
1567
|
'''
|
@@ -1588,13 +1588,40 @@ def showProgress(count: int, end: int, message: str, barLength: int = 100) -> No
|
|
1588
1588
|
percentStr = f'{percent:03.1f}'
|
1589
1589
|
filled = int(barLength * count / end)
|
1590
1590
|
bar = '█' * filled + '░' * (barLength - filled)
|
1591
|
-
print(f'\r{bar} {
|
1591
|
+
print(f'\r{message} |{bar}| {percent}% [{count}/{end}]', end='', flush=True)
|
1592
1592
|
if count == end:
|
1593
|
-
print(f'\r{bar} {
|
1593
|
+
print(f'\r{message} |{bar}| {percent}% [{count}/{end}] ', end='', flush=True)
|
1594
1594
|
print()
|
1595
1595
|
|
1596
1596
|
|
1597
|
-
def
|
1597
|
+
def dualProgress(primaryCount: int, primaryEnd: int,
|
1598
|
+
secondaryCount: int, secondaryEnd: int,
|
1599
|
+
barLength: int = 40,
|
1600
|
+
message: str = '') -> None:
|
1601
|
+
'''
|
1602
|
+
Draw two full progress bars every frame, overwriting previous frame.
|
1603
|
+
Bars are redrawn entirely each call.
|
1604
|
+
'''
|
1605
|
+
|
1606
|
+
primaryPercent = float(primaryCount / primaryEnd * 100) if primaryEnd > 0 else 100
|
1607
|
+
secondaryPercent = float(secondaryCount / secondaryEnd * 100) if secondaryEnd > 0 else 100
|
1608
|
+
|
1609
|
+
filledPrimary = int(barLength * primaryCount / primaryEnd) if primaryEnd > 0 else barLength
|
1610
|
+
filledSecondary = int((barLength - filledPrimary) * secondaryCount / secondaryEnd) if secondaryEnd > 0 else barLength
|
1611
|
+
|
1612
|
+
startSection = filledPrimary
|
1613
|
+
middleSection = filledSecondary
|
1614
|
+
endSection = barLength - startSection - middleSection
|
1615
|
+
|
1616
|
+
bar = '█' * startSection + '░' * middleSection + '-' * endSection
|
1617
|
+
formattedPrimaryPercent = f'{primaryPercent:03.1f}'
|
1618
|
+
formattedSecondaryPercent = f'{secondaryPercent:03.1f}'
|
1619
|
+
print(f'\r{bar} {formattedPrimaryPercent.rjust(6)}% | {formattedSecondaryPercent.rjust(6)}% | {message} ', end='', flush=True)
|
1620
|
+
if primaryCount == primaryEnd and secondaryCount == secondaryEnd:
|
1621
|
+
print(f'\r{bar} {formattedPrimaryPercent.rjust(6)}% | {formattedSecondaryPercent.rjust(6)}% ', end='', flush=True)
|
1622
|
+
|
1623
|
+
|
1624
|
+
def listAllFiles(folder: str, extension: str = "*") -> list:
|
1598
1625
|
list_of_files = []
|
1599
1626
|
# Getting the current work directory (cwd)
|
1600
1627
|
thisdir = folder
|
@@ -1616,7 +1643,7 @@ def listAllFiles(folder, extension="*"):
|
|
1616
1643
|
return list_of_files
|
1617
1644
|
|
1618
1645
|
|
1619
|
-
def clipFeatures(inputFeaturePath:str, boundaryFeature:str, outputFeature:str, keepOnlyTypes = None, v = False) -> geopandas.GeoDataFrame:
|
1646
|
+
def clipFeatures(inputFeaturePath:str, boundaryFeature:str, outputFeature:str, keepOnlyTypes: Optional[list] = None, v: bool = False) -> geopandas.GeoDataFrame:
|
1620
1647
|
'''
|
1621
1648
|
keepOnlyTypes = ['MultiPolygon', 'Polygon', 'Point', etc]
|
1622
1649
|
|
@@ -1996,7 +2023,7 @@ def getTimeseriesStats(data:pandas.DataFrame, observed:Optional[str] = None, sim
|
|
1996
2023
|
|
1997
2024
|
return stats
|
1998
2025
|
|
1999
|
-
def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None):
|
2026
|
+
def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None) -> Optional[pandas.DataFrame]:
|
2000
2027
|
'''
|
2001
2028
|
Read SWAT+ output files and return a pandas DataFrame with proper date handling
|
2002
2029
|
and optional filtering capabilities.
|
@@ -17,7 +17,7 @@ import pandas
|
|
17
17
|
|
18
18
|
# classes
|
19
19
|
class sqliteConnection:
|
20
|
-
def __init__(self, sqlite_database, connect = False):
|
20
|
+
def __init__(self, sqlite_database, connect = False) -> None:
|
21
21
|
self.db_name = sqlite_database
|
22
22
|
self.connection = None
|
23
23
|
self.cursor = None
|
@@ -25,13 +25,13 @@ class sqliteConnection:
|
|
25
25
|
if connect:
|
26
26
|
self.connect()
|
27
27
|
|
28
|
-
def connect(self, v=True):
|
28
|
+
def connect(self, v=True) -> None:
|
29
29
|
self.connection = sqlite3.connect(self.db_name)
|
30
30
|
self.cursor = self.connection.cursor()
|
31
31
|
if v:
|
32
32
|
self.report("\t-> connection to " + self.db_name + " established...")
|
33
33
|
|
34
|
-
def updateValue(self, table_name, col_name, new_value, col_where1, val_1, v=False):
|
34
|
+
def updateValue(self, table_name, col_name, new_value, col_where1, val_1, v=False) -> None:
|
35
35
|
"""
|
36
36
|
Updates a single value in a specified table where the condition matches.
|
37
37
|
|
@@ -56,7 +56,7 @@ class sqliteConnection:
|
|
56
56
|
raise Exception(f"Error updating value: {str(e)}")
|
57
57
|
|
58
58
|
|
59
|
-
def createTable(self, table_name, initial_field_name, data_type):
|
59
|
+
def createTable(self, table_name, initial_field_name, data_type) -> None:
|
60
60
|
'''
|
61
61
|
can be text, real, etc
|
62
62
|
'''
|
@@ -67,7 +67,100 @@ class sqliteConnection:
|
|
67
67
|
except:
|
68
68
|
self.report("\t! table exists")
|
69
69
|
|
70
|
-
def
|
70
|
+
def newTable(self, tableName, columnsDict, columnOrder=None, notNull=None, foreignKeys=None) -> None:
|
71
|
+
"""
|
72
|
+
Create a new table based on dictionary of columns and types.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
tableName (str): Name of the table to create
|
76
|
+
columnsDict (dict): Dictionary with column names as keys and data types as values
|
77
|
+
options:
|
78
|
+
INTEGER, REAL, TEXT, BLOB, etc.
|
79
|
+
Example: {'id': 'INTEGER PRIMARY KEY', 'name': 'TEXT', 'age': 'INTEGER'}
|
80
|
+
|
81
|
+
columnOrder (list, optional): List specifying the order of columns
|
82
|
+
Example: ['name', 'age', 'id']
|
83
|
+
|
84
|
+
notNull (list, optional): List of column names that should have NOT NULL constraint
|
85
|
+
Example: ['name', 'age']
|
86
|
+
|
87
|
+
foreignKeys (dict, optional): Dictionary defining foreign key constraints
|
88
|
+
Format Option 1: {currentTableField: {foreignTable: foreignField}, 'onDelete': 'CASCADE'}
|
89
|
+
Format Option 2: {currentTableField: {foreignTable: foreignField, 'onDelete': 'CASCADE'}}
|
90
|
+
|
91
|
+
onDelete options: CASCADE, SET NULL, RESTRICT, NO ACTION, SET DEFAULT
|
92
|
+
Examples:
|
93
|
+
{'user_id': {'users': 'id'}, 'onDelete': 'CASCADE'}
|
94
|
+
{'user_id': {'users': 'id', 'onDelete': 'SET NULL'}}
|
95
|
+
|
96
|
+
"""
|
97
|
+
try:
|
98
|
+
# Determine column order
|
99
|
+
if columnOrder:
|
100
|
+
# Use specified order, but include any columns not in the order list at the end
|
101
|
+
orderedColumns = []
|
102
|
+
for col in columnOrder:
|
103
|
+
if col in columnsDict:
|
104
|
+
orderedColumns.append(col)
|
105
|
+
# Add any remaining columns not in the order list
|
106
|
+
for col in columnsDict:
|
107
|
+
if col not in orderedColumns:
|
108
|
+
orderedColumns.append(col)
|
109
|
+
else:
|
110
|
+
orderedColumns = list(columnsDict.keys())
|
111
|
+
|
112
|
+
# Build column definitions
|
113
|
+
columnDefinitions = []
|
114
|
+
for col in orderedColumns:
|
115
|
+
definition = f"{col} {columnsDict[col]}"
|
116
|
+
|
117
|
+
# Add NOT NULL constraint if specified
|
118
|
+
if notNull and col in notNull:
|
119
|
+
definition += " NOT NULL"
|
120
|
+
|
121
|
+
columnDefinitions.append(definition)
|
122
|
+
|
123
|
+
# Handle foreign key constraints
|
124
|
+
if foreignKeys:
|
125
|
+
onDeleteClause = ""
|
126
|
+
if 'onDelete' in foreignKeys:
|
127
|
+
onDeleteClause = f" ON DELETE {foreignKeys['onDelete']}"
|
128
|
+
|
129
|
+
for currentField, foreignRef in foreignKeys.items():
|
130
|
+
if currentField != 'onDelete': # Skip the onDelete key
|
131
|
+
if isinstance(foreignRef, dict):
|
132
|
+
# Check if onDelete is specified at field level
|
133
|
+
if 'onDelete' in foreignRef:
|
134
|
+
fieldOnDelete = f" ON DELETE {foreignRef['onDelete']}"
|
135
|
+
# Get the actual foreign table and field (skip onDelete key)
|
136
|
+
foreignTable = None
|
137
|
+
foreignField = None
|
138
|
+
for key, value in foreignRef.items():
|
139
|
+
if key != 'onDelete':
|
140
|
+
foreignTable = key
|
141
|
+
foreignField = value
|
142
|
+
break
|
143
|
+
else:
|
144
|
+
# Use global onDelete clause and assume dict format {table: field}
|
145
|
+
fieldOnDelete = onDeleteClause
|
146
|
+
foreignTable = list(foreignRef.keys())[0]
|
147
|
+
foreignField = foreignRef[foreignTable]
|
148
|
+
|
149
|
+
if foreignTable and foreignField:
|
150
|
+
foreignKeyDef = f"FOREIGN KEY ({currentField}) REFERENCES {foreignTable}({foreignField}){fieldOnDelete}"
|
151
|
+
columnDefinitions.append(foreignKeyDef)
|
152
|
+
|
153
|
+
# Create the SQL statement
|
154
|
+
columnsSQL = ', '.join(columnDefinitions)
|
155
|
+
sql = f"CREATE TABLE {tableName} ({columnsSQL})"
|
156
|
+
|
157
|
+
self.cursor.execute(sql)
|
158
|
+
self.report(f"\t-> created table {tableName} in {self.db_name}")
|
159
|
+
|
160
|
+
except Exception as e:
|
161
|
+
self.report(f"\t! error creating table {tableName}: {str(e)}")
|
162
|
+
|
163
|
+
def renameTable(self, old_table_name, new_table_name, v=False) -> None:
|
71
164
|
"""
|
72
165
|
this function gives a new name to an existing table and saves the changes
|
73
166
|
"""
|
@@ -77,7 +170,7 @@ class sqliteConnection:
|
|
77
170
|
self.report("\t-> renamed " + old_table_name + " to " + new_table_name)
|
78
171
|
self.commitChanges()
|
79
172
|
|
80
|
-
def tableExists(self, table_name):
|
173
|
+
def tableExists(self, table_name) -> bool:
|
81
174
|
self.cursor.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='{table_name}'".format(
|
82
175
|
table_name=table_name))
|
83
176
|
if self.cursor.fetchone()[0] == 1:
|
@@ -85,7 +178,7 @@ class sqliteConnection:
|
|
85
178
|
else:
|
86
179
|
return False
|
87
180
|
|
88
|
-
def deleteRows(self, table_to_clean, col_where=None, col_where_value=None, v=False):
|
181
|
+
def deleteRows(self, table_to_clean, col_where=None, col_where_value=None, v=False) -> None:
|
89
182
|
"""
|
90
183
|
|
91
184
|
"""
|
@@ -103,17 +196,17 @@ class sqliteConnection:
|
|
103
196
|
if v:
|
104
197
|
self.report("\t-> removed all rows from " + table_to_clean)
|
105
198
|
|
106
|
-
def deleteTable(self, table_name):
|
199
|
+
def deleteTable(self, table_name) -> None:
|
107
200
|
"""
|
108
201
|
this function deletes the specified table
|
109
202
|
"""
|
110
203
|
self.cursor.execute('''DROP TABLE ''' + table_name)
|
111
204
|
self.report("\t-> deleted table " + table_name + " from " + self.db_name)
|
112
|
-
|
113
|
-
def dropTable(self, table_name):
|
205
|
+
|
206
|
+
def dropTable(self, table_name) -> None:
|
114
207
|
self.deleteTable(table_name)
|
115
208
|
|
116
|
-
def undoChanges(self):
|
209
|
+
def undoChanges(self) -> None:
|
117
210
|
"""
|
118
211
|
This function reverts the database to status before last commit
|
119
212
|
"""
|
@@ -121,7 +214,7 @@ class sqliteConnection:
|
|
121
214
|
self.connection.rollback()
|
122
215
|
self.commitChanges()
|
123
216
|
|
124
|
-
def readTableAsDict(self, table_name, key_column = 'id'):
|
217
|
+
def readTableAsDict(self, table_name, key_column: str = 'id') -> dict:
|
125
218
|
# Execute a SQL query to fetch all rows from your table
|
126
219
|
self.cursor = self.connection.execute(f"SELECT * FROM {table_name}")
|
127
220
|
|
@@ -134,7 +227,7 @@ class sqliteConnection:
|
|
134
227
|
|
135
228
|
return data
|
136
229
|
|
137
|
-
def getColumnsWithTypes(self, table_name):
|
230
|
+
def getColumnsWithTypes(self, table_name) -> dict:
|
138
231
|
c = self.cursor
|
139
232
|
|
140
233
|
# Prepare and execute a PRAGMA table_info statement
|
@@ -145,7 +238,7 @@ class sqliteConnection:
|
|
145
238
|
|
146
239
|
return columns_with_types
|
147
240
|
|
148
|
-
def insertDictPartial(self, table_name, data_dict):
|
241
|
+
def insertDictPartial(self, table_name, data_dict) -> None:
|
149
242
|
c = self.cursor
|
150
243
|
|
151
244
|
# Get the column names from the table
|
@@ -168,7 +261,7 @@ class sqliteConnection:
|
|
168
261
|
self.commitChanges()
|
169
262
|
|
170
263
|
|
171
|
-
def report(self, string, printing=False):
|
264
|
+
def report(self, string, printing=False) -> None:
|
172
265
|
if printing:
|
173
266
|
print(f"\t> {string}")
|
174
267
|
else:
|
@@ -176,7 +269,7 @@ class sqliteConnection:
|
|
176
269
|
sys.stdout.flush()
|
177
270
|
|
178
271
|
|
179
|
-
def createTableFromDict(self, table_name, columns_with_types):
|
272
|
+
def createTableFromDict(self, table_name, columns_with_types) -> None:
|
180
273
|
|
181
274
|
# Prepare a CREATE TABLE statement
|
182
275
|
fields = ', '.join(f'{column} {data_type}' for column, data_type in columns_with_types.items())
|
@@ -187,7 +280,7 @@ class sqliteConnection:
|
|
187
280
|
self.commitChanges()
|
188
281
|
|
189
282
|
|
190
|
-
def insertDict(self, table_name, data):
|
283
|
+
def insertDict(self, table_name, data) -> None:
|
191
284
|
|
192
285
|
# Prepare an INSERT INTO statement for each dictionary
|
193
286
|
for id, row in data.items():
|
@@ -204,7 +297,7 @@ class sqliteConnection:
|
|
204
297
|
|
205
298
|
|
206
299
|
|
207
|
-
def readTableColumns(self, table_name, column_list="all"):
|
300
|
+
def readTableColumns(self, table_name, column_list="all") -> list:
|
208
301
|
"""
|
209
302
|
this function takes a list to be a string separated by commmas and
|
210
303
|
a table and puts the columns in the table into a variable
|
@@ -225,7 +318,7 @@ class sqliteConnection:
|
|
225
318
|
self.report("\t-> read selected table columns from " + table_name)
|
226
319
|
return list_of_tuples
|
227
320
|
|
228
|
-
def insertField(self, table_name, field_name, data_type, to_new_line=False, messages=True):
|
321
|
+
def insertField(self, table_name, field_name, data_type, to_new_line=False, messages=True) -> None:
|
229
322
|
"""
|
230
323
|
This will insert a new field into your sqlite database
|
231
324
|
|
@@ -244,7 +337,7 @@ class sqliteConnection:
|
|
244
337
|
"\r\t-> inserted into table {0} field {1} ".format(table_name, field_name))
|
245
338
|
sys.stdout.flush()
|
246
339
|
|
247
|
-
def insertRow(self, table_name, ordered_content_list = [], dictionary_obj = {}, messages=False):
|
340
|
+
def insertRow(self, table_name, ordered_content_list = [], dictionary_obj = {}, messages=False) -> None:
|
248
341
|
"""
|
249
342
|
ordered_list such as ['ha','he','hi']
|
250
343
|
list should have data as strings
|
@@ -263,7 +356,7 @@ class sqliteConnection:
|
|
263
356
|
if messages:
|
264
357
|
self.report("\t-> inserted row into " + table_name)
|
265
358
|
|
266
|
-
def insertRows(self, table_name, list_of_tuples, messages=False):
|
359
|
+
def insertRows(self, table_name, list_of_tuples, messages=False) -> None:
|
267
360
|
"""
|
268
361
|
list_of_tuples such as [('ha','he','hi')'
|
269
362
|
('ha','he','hi')]
|
@@ -274,7 +367,7 @@ class sqliteConnection:
|
|
274
367
|
if messages:
|
275
368
|
self.report("\t-> inserted rows into " + table_name)
|
276
369
|
|
277
|
-
def dumpCSV(self, table_name, file_name, index=False, v=False):
|
370
|
+
def dumpCSV(self, table_name, file_name, index=False, v=False) -> None:
|
278
371
|
'''
|
279
372
|
save table to csv
|
280
373
|
'''
|
@@ -300,7 +393,11 @@ class sqliteConnection:
|
|
300
393
|
self.report(
|
301
394
|
"\t-> saved {0} changes to ".format(number_of_changes) + self.db_name)
|
302
395
|
|
303
|
-
def
|
396
|
+
def commit(self, v=False) -> None:
|
397
|
+
'''proxy to commitChanges'''
|
398
|
+
self.commitChanges(v)
|
399
|
+
|
400
|
+
def closeConnection(self, commit=True) -> None:
|
304
401
|
'''
|
305
402
|
disconnects from the database
|
306
403
|
'''
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|