Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning].

## [Unreleased]

## [0.0.24] - 2025-06-20

### Added in 0.0.24

- sz_update_project can now upgrade V3 -> V4 and V4 -> V4 projects

### Changed in 0.0.24

- Update tools from get_configs() to get_config_registry() (SDK change)
- sz_command now uses the setting and command `scroll` instead of `page`

### Fixed in 0.0.24

- sz_create_project shouldn't copy sz_update_project to a new project

## [0.0.23] - 2025-06-12

### Fixed in 0.0.23
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ disable = [
"consider-using-f-string",
"line-too-long",
"too-many-branches",
"too-many-locals"
"too-many-instance-attributes",
"too-many-locals",
"too-many-statements"
]
good-names = [
"template-python"
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
senzing==0.2.2
senzing==0.2.16
senzing-core==0.3.15
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = python-tools
version = 0.0.23
version = 0.0.24
author = senzing
author_email = support@senzing.com
description = Python Tools
Expand All @@ -21,8 +21,8 @@ package_dir =
packages = find:
python_requires = >=3.9
install_requires =
senzing >= 0.2.12
senzing-core >= 0.3.11
senzing >= 0.2.16
senzing-core >= 0.3.15

[options.packages.find]
where = src
188 changes: 188 additions & 0 deletions sz_tools/_project_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
"""Helpers for creating and updating projects"""

import json
import shutil
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from packaging import version as p_version

V3_BACKUP_PATH = "v3_to_v4_upgrade_backups"
V4_BUILD = "szBuildVersion.json"
SZ_SYS_PATH = Path("/opt/senzing")
V4_SYS_PATH = SZ_SYS_PATH / "er"
V4_DATA_PATH = SZ_SYS_PATH / "data"
V4_SYS_BUILD = V4_SYS_PATH / V4_BUILD

COPY_TO_PROJ = {
"er/": {"files": ["*"], "excludes": ["sz_create_project", "sz_update_project", "_project_helpers.py"]},
"data": {"files": ["*"], "excludes": []},
}

PERMISSIONS = {
".": {
"dir_pint": 0,
"file_pint": 0o660,
"files": ["LICENSE", "NOTICES", "README.1ST", "szBuildVersion.json"],
"excludes": ["setupEnv"],
"recursive": False,
},
"setupEnv": {"dir_pint": 0, "file_pint": 0o770, "files": [], "excludes": [], "recursive": False},
"bin": {
"dir_pint": 0o770,
"file_pint": 0o770,
"files": ["*"],
"excludes": ["__pycache__", "_sz_database.py", "_tool_helpers.py"],
"recursive": False,
},
"data": {"dir_pint": 0o770, "file_pint": 0o660, "files": ["*"], "excludes": [], "recursive": True},
"lib": {"dir_pint": 0o770, "file_pint": 0o660, "files": ["*"], "excludes": [], "recursive": False},
"resources": {"dir_pint": 0o770, "file_pint": 0o660, "files": ["*"], "excludes": ["setupEnv"], "recursive": True},
"resources/templates/setupEnv": {
"dir_pint": 0,
"file_pint": 0o770,
"files": [],
"excludes": [],
"recursive": False,
},
"sdk": {"dir_pint": 0o770, "file_pint": 0o660, "files": ["*"], "excludes": [], "recursive": True},
V3_BACKUP_PATH: {"dir_pint": 0o770, "file_pint": 0, "files": [], "excludes": [], "recursive": False},
}

PERMISSIONS_2 = {
"bin": {
"dir_pint": 0o770,
"file_pint": 0o660,
"files": ["_sz_database.py", "_tool_helpers.py"],
"excludes": [],
"recursive": False,
}
}


@dataclass()
class SzBuildDetails:
"""Build information for a project or Senzing SDK system install"""

platform: str
version: str
build_version: str
build_number: str
major: int = field(init=False)
minor: int = field(init=False)
micro: int = field(init=False)

def __post_init__(self) -> None:
self.version_parsed = p_version.parse(self.version)
self.build_version_parsed = p_version.parse(self.build_version)
self.major = self.version_parsed.major
self.minor = self.version_parsed.minor
self.micro = self.version_parsed.micro


def get_build_details(path: Path) -> SzBuildDetails:
"""Return dataclass with the details from a build file."""
try:
with open(path, "r", encoding="utf-8") as f:
# Ignore DATA_VERSION, V3 build files had it V4 doesn't
version_dict = {k.lower(): v for k, v in json.load(f).items() if k != "DATA_VERSION"}
except (OSError, json.JSONDecodeError) as err:
raise OSError(f"Couldn't get the build information from {path}: {err}") from err

return SzBuildDetails(**version_dict)


def copy_files_dirs(to_copy: dict[str, Any], source_dir: Path, target_dir: Path) -> None:
"""Copy files/directories within and to a project"""
for c_path, c_dict in to_copy.items():
excludes = c_dict["excludes"]
files = c_dict["files"]
source = source_dir / c_path

try:
if source.is_dir():
# If the key in to_copy ends with / copy everything in source dir to target_dir
# er/ as the key copies everything from /opt/senzing/er to target_dir
#
# If the key in to_copy doesn't end with / copy source dir and everything in it to target_dir
# data as the key copies /opt/senzing/data to target_dir/data
target = target_dir if c_path.endswith("/") else target_dir / c_path

# Copy entire contents of the source directory
if not files or (files and files[0] == "*"):
shutil.copytree(source, target, ignore=shutil.ignore_patterns(*excludes), dirs_exist_ok=True)

# Create the source directory in the target and only copy listed files
if files and files[0] != "*":
target.mkdir(exist_ok=True, parents=True)
for source_file in [source / f for f in files]:
shutil.copy(source_file, target / source_file.name)

if source.is_file():
# Single file copy always copies only the file, if the key to to_copy is er/szBuildVersion.json
# szBuildVersion.json is copied to target_dir and not target_dir/er/szBuildVersion.json
target = target_dir / source.name
target_dir.mkdir(exist_ok=True, parents=True)
shutil.copy(source, target)
except OSError as err:
raise OSError(f"Couldn't copy a file or directory: {err}") from err


def setup_env(proj_path: Path) -> None:
"""Create a new setupEnv and replace place holders with paths for the project"""
try:
shutil.copy(proj_path / "resources/templates/setupEnv", proj_path)
setup_path = proj_path / "setupEnv"

with open(setup_path, "r", encoding="utf-8") as in_:
data = in_.read()

data = data.replace("${SENZING_DIR}", str(proj_path)).replace("${SENZING_CONFIG_PATH}", str(proj_path / "etc"))

with open(setup_path, "w", encoding="utf-8") as out:
out.write(data)
except OSError as err:
raise OSError(f"Couldn't create a new setupEnv file: {err}") from err


def set_permissions(proj_path: Path, permissions: dict[str, dict[str, Any]]) -> None:
"""Set permissions for files/dirs copied to the project, or dirs removed and replaced completely e.g., data/"""
try:
for p_path, p_dict in permissions.items():
dir_pint = p_dict["dir_pint"]
file_pint = p_dict["file_pint"]
files = p_dict["files"]
recursive = p_dict["recursive"]
target = proj_path if p_path.startswith(".") else proj_path / p_path
excludes = [target / e for e in p_dict["excludes"]]

if target.is_dir():
if dir_pint != 0:
target.chmod(dir_pint)
d_chmods = (
[d for d in target.glob("*") if d.is_dir() and not d.is_symlink() and d not in excludes]
if not recursive
else [d for d in target.rglob("*") if d.is_dir() and not d.is_symlink() and d not in excludes]
)
for dir_ in d_chmods:
dir_.chmod(dir_pint)

if files and files[0] == "*":
f_chmods = (
[f for f in target.glob("*") if f.is_file() and not f.is_symlink() and f not in excludes]
if not recursive
else [f for f in target.rglob("*") if f.is_file() and not f.is_symlink() and f not in excludes]
)

for file in f_chmods:
Path(target / file).chmod(file_pint)

if files and files[0] != "*":
for file in files:
Path(target / file).chmod(file_pint)

if target.is_file():
target.chmod(file_pint)
except OSError as err:
raise OSError(f"Couldn't set a permission: {err}") from err
Loading
Loading