Troubleshooting pyinstaller.

This commit is contained in:
lwark
2024-07-29 08:21:47 -05:00
parent e85a4bba6f
commit f5e34991d7
8 changed files with 103 additions and 34 deletions

View File

@@ -112,14 +112,14 @@ This is meant to import .xslx files created from the Design & Analysis Software
## SETUP: ## SETUP:
## Download: ## Download and Setup:
*Python v3.11 or greater must be installed on your system for this.* *Python v3.11 or greater must be installed on your system for this.*
1. Clone or download from github. 1. Clone or download from github.
2. Enter the downloaded folder. 2. Enter the downloaded folder.
3. Open a terminal in the folder with the 'src' folder. 3. Open a terminal in the folder with the 'src' folder.
4. Create a new virtual environment: ```python -m venv venv``` 4. Create a new virtual environment: ```python -m venv .venv```
5. Activate the virtual environment: (Windows) ```venv\Scripts\activate.bat``` 5. Activate the virtual environment: (Windows) ```.venv\Scripts\activate.bat```
6. Install dependencies: ```pip install -r requirements.txt``` 6. Install dependencies: ```pip install -r requirements.txt```
## Database: ## Database:
@@ -133,6 +133,14 @@ This is meant to import .xslx files created from the Design & Analysis Software
## First Run: ## First Run:
1. On first run, the application copies src/config.yml to C:\Users\{USERNAME}\Local\submissions\config 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 2. If this folder cannot be found, C:\Users\{USERNAME}\Documents\submissions will be used.
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. 1. If using Postgres, the 'database_path' and other variables will have to be updated manually.
3. Initially, the config variables are set parsing the 'sqlalchemy.url' variable in alembic.ini
## Building Portable Application:
*Download and Setup must have been performed beforehand*
1. Using pyinstaller, an exe can be created.
2. Open a terminal in the folder with the 'src' folder.
3. Activate the virtual environment: (Windows) ```.venv\Scripts\activate.bat```
4. Enter the following command: ```pyinstaller .\submissions.spec --noconfirm```

47
docs/source/conf.py Normal file
View File

@@ -0,0 +1,47 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import sys
from pathlib import Path
sys.path.append(Path(__file__).parents[2].joinpath('src').absolute().__str__())
from submissions import __version__, __copyright__, __author__
project = 'RSL Submissions'
copyright = __copyright__
author = f"{__author__['name']} - {__author__['email']}"
release = __version__
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
master_doc = "index"
extensions = [
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.napoleon',
'sphinx_markdown_builder',
'sphinx_mdinclude',
]
templates_path = ['_templates']
exclude_patterns = []
sys.path.insert(0, Path(__file__).absolute().resolve().parents[2].joinpath("src").__str__())
sys.path.insert(0, Path(__file__).absolute().resolve().parents[2].joinpath("src/submissions").__str__())
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'alabaster'
# html_style = 'custom.css'
html_static_path = ['_static']
# autodoc_mock_imports = ["backend.db.models.submissions"]

Binary file not shown.

View File

@@ -1,5 +1,6 @@
import sys, os import sys, os
from tools import ctx, setup_logger, check_if_app from tools import ctx, setup_logger, check_if_app
# environment variable must be set to enable qtwebengine in network path # environment variable must be set to enable qtwebengine in network path
if check_if_app(): if check_if_app():
os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = "1" os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = "1"

View File

@@ -188,7 +188,10 @@ class App(QMainWindow):
current_month_bak = current_month_bak.with_suffix(".db") current_month_bak = current_month_bak.with_suffix(".db")
if not current_month_bak.exists() and "demo" not in self.ctx.database_path.__str__(): if not current_month_bak.exists() and "demo" not in self.ctx.database_path.__str__():
logger.info("No backup found for this month, backing up database.") logger.info("No backup found for this month, backing up database.")
shutil.copyfile(self.ctx.database_path, current_month_bak) try:
shutil.copyfile(self.ctx.backup_path, current_month_bak)
except PermissionError as e:
logger.error(f"Couldn't backup database due to: {e}")
case "postgresql+psycopg2": case "postgresql+psycopg2":
logger.warning(f"Backup function not yet implemented for psql") logger.warning(f"Backup function not yet implemented for psql")
current_month_bak = current_month_bak.with_suffix(".psql") current_month_bak = current_month_bak.with_suffix(".psql")

View File

@@ -85,7 +85,10 @@ class ControlsViewer(QWidget):
self.mode = self.mode_typer.currentText() self.mode = self.mode_typer.currentText()
self.sub_typer.clear() self.sub_typer.clear()
# NOTE: lookup subtypes # NOTE: lookup subtypes
sub_types = ControlType.query(name=self.con_type).get_subtypes(mode=self.mode) try:
sub_types = ControlType.query(name=self.con_type).get_subtypes(mode=self.mode)
except AttributeError:
sub_types = []
if sub_types != []: if sub_types != []:
# NOTE: block signal that will rerun controls getter and update sub_typer # NOTE: block signal that will rerun controls getter and update sub_typer
with QSignalBlocker(self.sub_typer) as blocker: with QSignalBlocker(self.sub_typer) as blocker:

View File

@@ -5,9 +5,7 @@ from __future__ import annotations
import json import json
import pprint import pprint
import weakref
from json import JSONDecodeError from json import JSONDecodeError
import jinja2
import numpy as np import numpy as np
import logging, re, yaml, sys, os, stat, platform, getpass, inspect, csv import logging, re, yaml, sys, os, stat, platform, getpass, inspect, csv
import pandas as pd import pandas as pd
@@ -15,7 +13,7 @@ from jinja2 import Environment, FileSystemLoader
from logging import handlers from logging import handlers
from pathlib import Path from pathlib import Path
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import create_engine, text from sqlalchemy import create_engine, text, MetaData
from pydantic import field_validator, BaseModel, Field from pydantic import field_validator, BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Any, Tuple, Literal, List from typing import Any, Tuple, Literal, List
@@ -302,11 +300,11 @@ class Settings(BaseSettings, extra="allow"):
check = value.exists() check = value.exists()
except AttributeError: except AttributeError:
check = False check = False
if not check: #and values.data['database_schema'] == "sqlite": if not check: # and values.data['database_schema'] == "sqlite":
# print(f"No directory found, using Documents/submissions") # print(f"No directory found, using Documents/submissions")
# value = Path.home().joinpath("Documents", "submissions") # value = Path.home().joinpath("Documents", "submissions")
value.mkdir(exist_ok=True) value.mkdir(exist_ok=True)
print(f"Final return of directory_path: {value}") # print(f"Final return of directory_path: {value}")
return value return value
@field_validator('database_path', mode="before") @field_validator('database_path', mode="before")
@@ -314,8 +312,8 @@ class Settings(BaseSettings, extra="allow"):
def ensure_database_exists(cls, value, values): def ensure_database_exists(cls, value, values):
# if value == ":memory:": # if value == ":memory:":
# return value # return value
# and values.data['database_schema'] == "sqlite": # and values.data['database_schema'] == "sqlite":
# value = values.data['directory_path'] # value = values.data['directory_path']
match values.data['database_schema']: match values.data['database_schema']:
case "sqlite": case "sqlite":
if value is None: if value is None:
@@ -366,7 +364,7 @@ class Settings(BaseSettings, extra="allow"):
alembic_path = project_path.joinpath("alembic.ini") alembic_path = project_path.joinpath("alembic.ini")
# print(f"Getting alembic path: {alembic_path}") # print(f"Getting alembic path: {alembic_path}")
value = cls.get_alembic_db_path(alembic_path=alembic_path, mode='user') value = cls.get_alembic_db_path(alembic_path=alembic_path, mode='user')
print(f"Got {value} for user") # print(f"Got {value} for user")
return value return value
@field_validator("database_password", mode='before') @field_validator("database_password", mode='before')
@@ -379,10 +377,9 @@ class Settings(BaseSettings, extra="allow"):
alembic_path = project_path.joinpath("alembic.ini") alembic_path = project_path.joinpath("alembic.ini")
# print(f"Getting alembic path: {alembic_path}") # print(f"Getting alembic path: {alembic_path}")
value = cls.get_alembic_db_path(alembic_path=alembic_path, mode='pass') value = cls.get_alembic_db_path(alembic_path=alembic_path, mode='pass')
print(f"Got {value} for pass") # print(f"Got {value} for pass")
return value return value
@field_validator('database_session', mode="before") @field_validator('database_session', mode="before")
@classmethod @classmethod
def create_database_session(cls, value, values): def create_database_session(cls, value, values):
@@ -395,8 +392,9 @@ class Settings(BaseSettings, extra="allow"):
value = f"/{values.data['database_path']}" value = f"/{values.data['database_path']}"
db_name = f"{values.data['database_name']}.db" db_name = f"{values.data['database_name']}.db"
case _: case _:
print(pprint.pprint(values.data)) # print(pprint.pprint(values.data))
tmp = jinja_template_loading().from_string("{% if values['database_user'] %}{{ values['database_user'] }}{% if values['database_password'] %}:{{ values['database_password'] }}{% endif %}{% endif %}@{{ values['database_path'] }}") tmp = jinja_template_loading().from_string(
"{% if values['database_user'] %}{{ values['database_user'] }}{% if values['database_password'] %}:{{ values['database_password'] }}{% endif %}{% endif %}@{{ values['database_path'] }}")
value = tmp.render(values=values.data) value = tmp.render(values=values.data)
db_name = values.data['database_name'] db_name = values.data['database_name']
template = jinja_template_loading().from_string( template = jinja_template_loading().from_string(
@@ -422,7 +420,7 @@ class Settings(BaseSettings, extra="allow"):
# database_path = database_path # database_path = database_path
# else: # else:
# raise FileNotFoundError("No database file found. Exiting program.") # raise FileNotFoundError("No database file found. Exiting program.")
print(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(f"sqlite:///{database_path}") #, echo=True, future=True)
# engine = create_engine("postgresql+psycopg2://postgres:RE,4321q@localhost:5432/submissions") # engine = create_engine("postgresql+psycopg2://postgres:RE,4321q@localhost:5432/submissions")
engine = create_engine(database_path) engine = create_engine(database_path)
@@ -439,15 +437,22 @@ class Settings(BaseSettings, extra="allow"):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.set_from_db(db_path=kwargs['database_path']) self.set_from_db()
def set_from_db(self, db_path: Path): def set_from_db(self):
if 'pytest' in sys.modules: if 'pytest' in sys.modules:
output = dict(power_users=['lwark', 'styson', 'ruwang']) output = dict(power_users=['lwark', 'styson', 'ruwang'])
else: else:
# session = Session(create_engine(f"sqlite:///{db_path}")) # session = Session(create_engine(f"sqlite:///{db_path}"))
logger.debug(self.__dict__) logger.debug(self.__dict__)
session = self.database_session session = self.database_session
metadata = MetaData()
try:
tables = metadata.reflect(bind=session.get_bind()).tables.keys()
except AttributeError:
return
if "_configitem" not in tables:
return
config_items = session.execute(text("SELECT * FROM _configitem")).all() config_items = session.execute(text("SELECT * FROM _configitem")).all()
session.close() session.close()
# print(config_items) # print(config_items)
@@ -480,14 +485,14 @@ class Settings(BaseSettings, extra="allow"):
try: try:
return url[:url.index("@")].split(":")[0] return url[:url.index("@")].split(":")[0]
except (IndexError, ValueError) as e: except (IndexError, ValueError) as e:
print(f"Error on user: {e}") # print(f"Error on user: {e}")
return None return None
case "pass": case "pass":
url = re.sub(r"^.*//", "", url) url = re.sub(r"^.*//", "", url)
try: try:
return url[:url.index("@")].split(":")[1] return url[:url.index("@")].split(":")[1]
except (IndexError, ValueError) as e: except (IndexError, ValueError) as e:
print(f"Error on user: {e}") # print(f"Error on user: {e}")
return None return None
def save(self, settings_path: Path): def save(self, settings_path: Path):
@@ -498,18 +503,18 @@ class Settings(BaseSettings, extra="allow"):
continue continue
match v: match v:
case Path(): case Path():
print("Path") # print("Path")
if v.is_dir(): if v.is_dir():
print("dir") # print("dir")
v = v.absolute().__str__() v = v.absolute().__str__()
elif v.is_file(): elif v.is_file():
print("file") # print("file")
v = v.parent.absolute().__str__() v = v.parent.absolute().__str__()
else: else:
v = v.__str__() v = v.__str__()
case _: case _:
pass pass
print(f"Key: {k}, Value: {v}") # print(f"Key: {k}, Value: {v}")
dicto[k] = v dicto[k] = v
with open(settings_path, 'w') as f: with open(settings_path, 'w') as f:
yaml.dump(dicto, f) yaml.dump(dicto, f)
@@ -570,7 +575,7 @@ def get_config(settings_path: Path | str | None = None) -> Settings:
# settings = 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 = Settings(**default_settings)
settings.save(settings_path=CONFIGDIR.joinpath("config.yml")) settings.save(settings_path=CONFIGDIR.joinpath("config.yml"))
print(f"Default settings: {pprint.pprint(settings.__dict__)}") # print(f"Default settings: {pprint.pprint(settings.__dict__)}")
return settings return settings
else: else:
# NOTE: check if user defined path is directory # NOTE: check if user defined path is directory
@@ -758,7 +763,7 @@ def jinja_template_loading() -> Environment:
if check_if_app(): if check_if_app():
loader_path = Path(sys._MEIPASS).joinpath("files", "templates") loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
else: else:
loader_path = Path(__file__).parent.joinpath('templates').absolute() #.__str__() loader_path = Path(__file__).parents[1].joinpath('templates').absolute() # .__str__()
# NOTE: jinja template loading # NOTE: jinja template loading
loader = FileSystemLoader(loader_path) loader = FileSystemLoader(loader_path)
env = Environment(loader=loader) env = Environment(loader=loader)

View File

@@ -7,14 +7,16 @@ import sys, subprocess
from pathlib import Path from pathlib import Path
sys.path.append(Path(".").parent.joinpath('src').absolute().__str__()) sys.path.append(Path(".").parent.joinpath('src').absolute().__str__())
from submissions import __version__, __project__, bcolors, project_path from submissions import __version__, __project__, bcolors, project_path
print(f"Using {project_path} as project path.")
doc_path = project_path.joinpath("docs").absolute() doc_path = project_path.joinpath("docs").absolute()
build_path = project_path.joinpath(".venv", "Scripts", "sphinx-build").absolute().__str__() build_path = project_path.joinpath(".venv", "Scripts", "sphinx-build").absolute().__str__()
print(bcolors.BOLD + "Running Sphinx subprocess to generate rst files..." + bcolors.ENDC) print(bcolors.BOLD + "Running Sphinx subprocess to generate rst files..." + bcolors.ENDC)
api_path = project_path.joinpath(".venv", "Scripts", "sphinx-apidoc").absolute().__str__() 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"]) 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) 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"]) docs_build = doc_path.joinpath("build")
#docs_build.mkdir(exist_ok=True, parents=True)
subprocess.run([build_path, doc_path.joinpath("source").__str__(), docs_build.__str__(), "-a"])
######################################################### #########################################################
a = Analysis( a = Analysis(