Increased flexibility and privacy.
This commit is contained in:
15
README.md
15
README.md
@@ -113,13 +113,26 @@ This is meant to import .xslx files created from the Design & Analysis Software
|
||||
## SETUP:
|
||||
|
||||
## Download:
|
||||
*Python v3.11 or greater must be installed on your system for this.*
|
||||
|
||||
1. Clone or download from github.
|
||||
2. Enter the downloaded folder.
|
||||
3. Open a terminal in the folder with the 'src' folder.
|
||||
4. Create a new virtual environment: ```python -m venv venv```
|
||||
5. Activate the virtual environment: (Windows) ```venv\Scripts\activate.bat```
|
||||
6. Install dependencies: ```pip install -r requirements.txt```
|
||||
|
||||
## Database:
|
||||
|
||||
1. Copy 'alembic_default.ini' to 'alembic.ini' in the same folder.
|
||||
2. Open 'alembic.ini' and edit 'sqlalchemy.url' to the desired path of the database.
|
||||
1. The path by default is sqlite based. Postgresql support is available.
|
||||
2. Postgres path
|
||||
3. Open a terminal in the folder with the 'src' folder.
|
||||
4. Run database migration: ```alembic upgrade head```
|
||||
|
||||
## First Run:
|
||||
|
||||
1. On first run, the application copies src/config.yml to C:\Users\{USERNAME}\Local\submissions\config
|
||||
2. Initially, the 'directory_path' variable is set to the 'sqlalchemy.url' variable in alembic.ini
|
||||
3. If this folder cannot be found, C:\Users\{USERNAME}\Documents\submissions will be used.
|
||||
1. If using Postgres, the 'database_path' and other variables will have to be updated manually.
|
||||
@@ -55,7 +55,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
||||
sqlalchemy.url = sqlite:///{{PUT DATABASE PATH HERE!!}}
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
|
||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -229,7 +229,7 @@ class SubmissionsSheet(QTableView):
|
||||
self.report.add_result(Result(msg=f"We added {count} logs to the database.", status='Information'))
|
||||
|
||||
@report_result
|
||||
def generate_report(self):
|
||||
def generate_report(self, *args):
|
||||
"""
|
||||
Make a report
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,8 @@ Contains miscellaenous functions used by both frontend and backend.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pprint
|
||||
import weakref
|
||||
from json import JSONDecodeError
|
||||
import jinja2
|
||||
import numpy as np
|
||||
@@ -21,11 +23,14 @@ from PyQt6.QtGui import QPageSize
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
from PyQt6.QtPrintSupport import QPrinter
|
||||
from __init__ import project_path
|
||||
from configparser import ConfigParser
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
package_dir = Path(__file__).parents[2].resolve()
|
||||
logger.debug(f"Package dir: {package_dir}")
|
||||
# package_dir = Path(__file__).parents[2].resolve()
|
||||
# package_dir = project_path
|
||||
logger.debug(f"Package dir: {project_path}")
|
||||
|
||||
if platform.system() == "Windows":
|
||||
os_config_dir = "AppData/local"
|
||||
@@ -224,7 +229,7 @@ class Settings(BaseSettings, extra="allow"):
|
||||
|
||||
"""
|
||||
database_schema: str
|
||||
directory_path: Path
|
||||
directory_path: Path | None = None
|
||||
database_user: str | None = None
|
||||
database_password: str | None = None
|
||||
database_name: str
|
||||
@@ -255,11 +260,27 @@ class Settings(BaseSettings, extra="allow"):
|
||||
@field_validator('directory_path', mode="before")
|
||||
@classmethod
|
||||
def ensure_directory_exists(cls, value):
|
||||
if value is None:
|
||||
print("No value for dir path")
|
||||
if check_if_app():
|
||||
alembic_path = Path(sys._MEIPASS).joinpath("files", "alembic.ini")
|
||||
else:
|
||||
alembic_path = project_path.joinpath("alembic.ini")
|
||||
print(f"Getting alembic path: {alembic_path}")
|
||||
value = cls.get_alembic_db_path(alembic_path=alembic_path)
|
||||
print(f"Using {value}")
|
||||
if isinstance(value, str):
|
||||
value = Path(value)
|
||||
if not value.exists():
|
||||
value = Path().home()
|
||||
# metadata.directory_path = value
|
||||
try:
|
||||
check = value.exists()
|
||||
except AttributeError:
|
||||
check = False
|
||||
if not check:
|
||||
print(f"No directory found, using Documents/submissions")
|
||||
value = Path.home().joinpath("Documents", "submissions")
|
||||
value.mkdir()
|
||||
# metadata.directory_path = value
|
||||
print(f"Final return of directory_path: {value}")
|
||||
return value
|
||||
|
||||
@field_validator('database_path', mode="before")
|
||||
@@ -267,12 +288,14 @@ class Settings(BaseSettings, extra="allow"):
|
||||
def ensure_database_exists(cls, value, values):
|
||||
# if value == ":memory:":
|
||||
# return value
|
||||
if value is None:
|
||||
value = values.data['directory_path']
|
||||
match values.data['database_schema']:
|
||||
case "sqlite":
|
||||
value = f"/{Path(value).absolute().__str__()}/{values.data['database_name']}.db"
|
||||
value = Path(f"{Path(value).absolute().__str__()}/{values.data['database_name']}.db")
|
||||
# db_name = f"{values.data['database_name']}.db"
|
||||
case _:
|
||||
value = f"@{value}/{values.data['database_name']}"
|
||||
value = f"{value}/{values.data['database_name']}"
|
||||
# db_name = values.data['database_name']
|
||||
# match value:
|
||||
# case str():
|
||||
@@ -283,7 +306,6 @@ class Settings(BaseSettings, extra="allow"):
|
||||
# return value
|
||||
# else:
|
||||
# raise FileNotFoundError(f"Couldn't find database at {value}")
|
||||
|
||||
return value
|
||||
|
||||
@field_validator('database_session', mode="before")
|
||||
@@ -292,9 +314,15 @@ class Settings(BaseSettings, extra="allow"):
|
||||
if value is not None:
|
||||
return value
|
||||
else:
|
||||
match values.data['database_schema']:
|
||||
case "sqlite":
|
||||
value = f"/{values.data['database_path']}"
|
||||
# db_name = f"{values.data['database_name']}.db"
|
||||
case _:
|
||||
value = f"@{values.data['database_path']}"
|
||||
template = jinja_template_loading().from_string(
|
||||
"{{ values['database_schema'] }}://{% if values['database_user'] %}{{ values['database_user'] }}{% if values['database_password'] %}:{{ values['database_password'] }}{% endif %}{% endif %}{{ values['database_path'] }}")
|
||||
database_path = template.render(values=values.data)
|
||||
"{{ values['database_schema'] }}://{% if values['database_user'] %}{{ values['database_user'] }}{% if values['database_password'] %}:{{ values['database_password'] }}{% endif %}{% endif %}{{ value }}")
|
||||
database_path = template.render(values=values.data, value=value)
|
||||
# print(f"Using {database_path} for database path")
|
||||
# database_path = values.data['database_path']
|
||||
# if database_path is None:
|
||||
@@ -303,7 +331,7 @@ class Settings(BaseSettings, extra="allow"):
|
||||
# database_path = Path.home().joinpath(".submissions", "submissions.db")
|
||||
# # NOTE: finally, look in the local dir
|
||||
# else:
|
||||
# database_path = package_dir.joinpath("submissions.db")
|
||||
# database_path = project_path.joinpath("submissions.db")
|
||||
# else:
|
||||
# if database_path == ":memory:":
|
||||
# pass
|
||||
@@ -315,7 +343,7 @@ class Settings(BaseSettings, extra="allow"):
|
||||
# database_path = database_path
|
||||
# else:
|
||||
# raise FileNotFoundError("No database file found. Exiting program.")
|
||||
logger.info(f"Using {database_path} for database file.")
|
||||
print(f"Using {database_path} for database file.")
|
||||
# engine = create_engine(f"sqlite:///{database_path}") #, echo=True, future=True)
|
||||
# engine = create_engine("postgresql+psycopg2://postgres:RE,4321q@localhost:5432/submissions")
|
||||
engine = create_engine(database_path)
|
||||
@@ -334,11 +362,13 @@ class Settings(BaseSettings, extra="allow"):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_from_db(db_path=kwargs['database_path'])
|
||||
|
||||
|
||||
def set_from_db(self, db_path: Path):
|
||||
if 'pytest' in sys.modules:
|
||||
output = dict(power_users=['lwark', 'styson', 'ruwang'])
|
||||
else:
|
||||
# session = Session(create_engine(f"sqlite:///{db_path}"))
|
||||
logger.debug(self.__dict__)
|
||||
session = self.database_session
|
||||
config_items = session.execute(text("SELECT * FROM _configitem")).all()
|
||||
session.close()
|
||||
@@ -354,6 +384,36 @@ class Settings(BaseSettings, extra="allow"):
|
||||
if not hasattr(self, k):
|
||||
self.__setattr__(k, v)
|
||||
|
||||
@classmethod
|
||||
def get_alembic_db_path(cls, alembic_path) -> Path:
|
||||
c = ConfigParser()
|
||||
c.read(alembic_path)
|
||||
path = c['alembic']['sqlalchemy.url'].replace("sqlite:///", "")
|
||||
return Path(path).parent
|
||||
|
||||
def save(self, settings_path:Path):
|
||||
if not settings_path.exists():
|
||||
dicto = {}
|
||||
for k,v in self.__dict__.items():
|
||||
if k in ['package', 'database_session']:
|
||||
continue
|
||||
match v:
|
||||
case Path():
|
||||
print("Path")
|
||||
if v.is_dir():
|
||||
print("dir")
|
||||
v = v.absolute().__str__()
|
||||
elif v.is_file():
|
||||
print("file")
|
||||
v = v.parent.absolute().__str__()
|
||||
case _:
|
||||
pass
|
||||
print(f"Key: {k}, Value: {v}")
|
||||
dicto[k] = v
|
||||
with open(settings_path, 'w') as f:
|
||||
yaml.dump(dicto, f)
|
||||
# return settings
|
||||
|
||||
|
||||
def get_config(settings_path: Path | str | None = None) -> Settings:
|
||||
"""
|
||||
@@ -400,12 +460,17 @@ def get_config(settings_path: Path | str | None = None) -> Settings:
|
||||
if check_if_app():
|
||||
settings_path = Path(sys._MEIPASS).joinpath("files", "config.yml")
|
||||
else:
|
||||
settings_path = package_dir.joinpath('src', 'config.yml')
|
||||
settings_path = project_path.joinpath('src', 'config.yml')
|
||||
with open(settings_path, "r") as dset:
|
||||
default_settings = yaml.load(dset, Loader=yaml.Loader)
|
||||
|
||||
# NOTE: Tell program we need to copy the config.yml to the user directory
|
||||
# NOTE: copy settings to config directory
|
||||
return Settings(**copy_settings(settings_path=CONFIGDIR.joinpath("config.yml"), settings=default_settings))
|
||||
# settings = Settings(**copy_settings(settings_path=CONFIGDIR.joinpath("config.yml"), settings=default_settings))
|
||||
settings = Settings(**default_settings)
|
||||
settings.save(settings_path=CONFIGDIR.joinpath("config.yml"))
|
||||
print(f"Default settings: {pprint.pprint(settings.__dict__)}")
|
||||
return settings
|
||||
else:
|
||||
# NOTE: check if user defined path is directory
|
||||
if settings_path.is_dir():
|
||||
@@ -417,7 +482,9 @@ def get_config(settings_path: Path | str | None = None) -> Settings:
|
||||
logger.error("No config.yml file found. Writing to directory.")
|
||||
with open(settings_path, "r") as dset:
|
||||
default_settings = yaml.load(dset, Loader=yaml.Loader)
|
||||
return Settings(**copy_settings(settings_path=settings_path, settings=default_settings))
|
||||
# return Settings(**copy_settings(settings_path=settings_path, settings=default_settings))
|
||||
settings = Settings(**default_settings)
|
||||
settings.save(settings_path=settings_path)
|
||||
# logger.debug(f"Using {settings_path} for config file.")
|
||||
with open(settings_path, "r") as stream:
|
||||
settings = yaml.load(stream, Loader=yaml.Loader)
|
||||
|
||||
73
submissions.spec
Normal file
73
submissions.spec
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
#### custom for automation of documentation building ####
|
||||
import sys, subprocess
|
||||
from pathlib import Path
|
||||
sys.path.append(Path(".").parent.joinpath('src').absolute().__str__())
|
||||
from submissions import __version__, __project__, bcolors, project_path
|
||||
|
||||
doc_path = project_path.joinpath("docs").absolute()
|
||||
build_path = project_path.joinpath(".venv", "Scripts", "sphinx-build").absolute().__str__()
|
||||
print(bcolors.BOLD + "Running Sphinx subprocess to generate rst files..." + bcolors.ENDC)
|
||||
api_path = project_path.joinpath(".venv", "Scripts", "sphinx-apidoc").absolute().__str__()
|
||||
subprocess.run([api_path, "-o", doc_path.joinpath("source").__str__(), project_path.joinpath("src", "submissions").__str__(), "-f"])
|
||||
print(bcolors.BOLD + "Running Sphinx subprocess to generate html docs..." + bcolors.ENDC)
|
||||
subprocess.run([build_path, doc_path.joinpath("source").__str__(), doc_path.joinpath("build").__str__(), "-a"])
|
||||
#########################################################
|
||||
|
||||
a = Analysis(
|
||||
['src\\submissions\\__main__.py'],
|
||||
pathex=[project_path.absolute().__str__()],
|
||||
binaries=[],
|
||||
datas=[
|
||||
("src\\config.yml", "files"),
|
||||
("src\\submissions\\templates\\*", "files\\templates"),
|
||||
("src\\submissions\\templates\\css\\*", "files\\templates\\css"),
|
||||
("docs\\build", "files\\docs"),
|
||||
("src\\submissions\\resources\\*", "files\\resources"),
|
||||
("alembic.ini", "files"),
|
||||
],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=["*.xlsx"],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name=f"{__project__}_{__version__}",
|
||||
debug=True,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
# Change these for non-beta versions
|
||||
#console=False,
|
||||
#disable_windowed_traceback=True,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name=f"{__project__}_{__version__}",
|
||||
)
|
||||
Reference in New Issue
Block a user