+iLibrary.src
+
+
+
+
+
+
+ 1
+From 60f75ab41e3886e8826cb7468d51302a73d5954a Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:05:32 +0100
Subject: [PATCH 01/31] fixxes Docs Test
---
.github/workflows/docs.yml | 20 +++++---------------
.github/workflows/python-package.yml | 2 +-
2 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 28672b6..79ddefe 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -11,51 +11,41 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
-
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@v6
with:
- python-version: "3.12"
+ python-version: "3.14"
- # Install dependencies from your package directory
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ./app/iLibrary
- # Install documentation tool
- - name: Install pdoc
+ - name: Install documentation tool
run: pip install pdoc
- # Build documentation
- name: Build documentation
run: |
- python -m pip install --upgrade pip
- pip install -e ./app/iLibrary
- pip install pdoc
pdoc app.iLibrary.src -o docs
touch docs/.nojekyll
- - uses: actions/upload-pages-artifact@v3
+ - uses: actions/upload-pages-artifact@v4
with:
path: docs/
deploy:
needs: build
runs-on: ubuntu-latest
-
permissions:
pages: write
id-token: write
-
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
-
steps:
- id: deployment
uses: actions/deploy-pages@v4
\ No newline at end of file
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 9ded05a..a644e67 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["3.9", "3.10", "3.11"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
# 1. Inject SECRETS as Environment Variables for the entire job
env:
From 2050e89c9fb599738616329ca79dc1f846deb9b2 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:06:09 +0100
Subject: [PATCH 02/31] fixxes Docs Test
---
.github/workflows/docs.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 79ddefe..49568f5 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
+ - Developer
permissions:
contents: read
From 4a735c8fc2987fb8762bf0e6216ccee6b7790760 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:07:21 +0100
Subject: [PATCH 03/31] fixxes Docs Test
---
.github/workflows/docs.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 49568f5..1296a3e 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -24,7 +24,7 @@ jobs:
- name: Install package
run: |
python -m pip install --upgrade pip
- pip install -e ./app/iLibrary
+ pip install -e .
- name: Install documentation tool
run: pip install pdoc
From db56f98adbe4e0e735009603fb39586de28f5529 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:09:34 +0100
Subject: [PATCH 04/31] fixxes Docs Test
---
.github/workflows/docs.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 1296a3e..49d09d0 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -31,7 +31,10 @@ jobs:
- name: Build documentation
run: |
- pdoc app.iLibrary.src -o docs
+ # Add your src folder to PYTHONPATH so Python can find the module
+ export PYTHONPATH=$(pwd)/app/iLibrary/src
+ # Use the actual module name inside src
+ pdoc ilibrary -o docs
touch docs/.nojekyll
- uses: actions/upload-pages-artifact@v4
From a6540632d2f1d82b362a2eda95d776e8bd1676d4 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:10:55 +0100
Subject: [PATCH 05/31] fixxes Docs Test
---
.github/workflows/docs.yml | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 49d09d0..16d9e5d 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -31,10 +31,8 @@ jobs:
- name: Build documentation
run: |
- # Add your src folder to PYTHONPATH so Python can find the module
- export PYTHONPATH=$(pwd)/app/iLibrary/src
- # Use the actual module name inside src
- pdoc ilibrary -o docs
+ pip install pdoc
+ pdoc ./app/iLibrary/src/ilibrary --output-dir docs --force
touch docs/.nojekyll
- uses: actions/upload-pages-artifact@v4
From 2e12624a590927e69e58e3d65912a97c2782d996 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:11:38 +0100
Subject: [PATCH 06/31] fixxes Docs Test
---
.github/workflows/docs.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 16d9e5d..4e0ced9 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -32,7 +32,7 @@ jobs:
- name: Build documentation
run: |
pip install pdoc
- pdoc ./app/iLibrary/src/ilibrary --output-dir docs --force
+ pdoc ./app/iLibrary/src/ilibrary -o docs
touch docs/.nojekyll
- uses: actions/upload-pages-artifact@v4
From fd8768d985a0b81a43d6f0343b8d142c7e37e0a5 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:17:45 +0100
Subject: [PATCH 07/31] fixxes Docs Test
---
setup.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/setup.py b/setup.py
index 3fb7d7f..79475ef 100644
--- a/setup.py
+++ b/setup.py
@@ -25,9 +25,9 @@
package_dir = {"": "app"},
packages = find_packages(where="app"),
python_requires = ">=3.6",
- # install_requires=[
- # "requests>=2.25.0",
- # "pandas>=1.2.0",
- # "pyodbc", # Often used for IBM i connectivity
- # ],
+ install_requires=[
+ "paramiko",
+ "pyodbc",
+ "python-dotenv",
+ ],
)
\ No newline at end of file
From 5aee43a75bddf3ce061799fe6401ee9e78e8aa63 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:20:20 +0100
Subject: [PATCH 08/31] fixxes Docs Test
---
setup.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index 79475ef..812b7d4 100644
--- a/setup.py
+++ b/setup.py
@@ -22,8 +22,8 @@
"Development Status :: 4 - Beta",
"Operating System :: OS Independent",
],
- package_dir = {"": "app"},
- packages = find_packages(where="app"),
+ package_dir={"": "app/iLibrary/src"},
+ packages=find_packages(where="app/iLibrary/src"),
python_requires = ">=3.6",
install_requires=[
"paramiko",
From 695f82073411eae01d3121d8540eb6575b53d829 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:22:06 +0100
Subject: [PATCH 09/31] fixxes Docs Test
---
.github/workflows/docs.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 4e0ced9..75e6f4a 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -21,18 +21,15 @@ jobs:
with:
python-version: "3.14"
- - name: Install package
+ - name: Install package and dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
- - name: Install documentation tool
- run: pip install pdoc
-
- name: Build documentation
run: |
pip install pdoc
- pdoc ./app/iLibrary/src/ilibrary -o docs
+ pdoc ilibrary -o docs
touch docs/.nojekyll
- uses: actions/upload-pages-artifact@v4
From 6839bb4af9a7baa5c75df2733b9d333169145cb5 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:24:13 +0100
Subject: [PATCH 10/31] fixxes Docs Test
---
.github/workflows/docs.yml | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 75e6f4a..2604705 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -21,15 +21,17 @@ jobs:
with:
python-version: "3.14"
- - name: Install package and dependencies
+ - name: Install package
run: |
python -m pip install --upgrade pip
pip install -e .
+ - name: Install documentation tool
+ run: pip install pdoc
+
- name: Build documentation
run: |
- pip install pdoc
- pdoc ilibrary -o docs
+ PYTHONPATH=app/iLibrary/src pdoc ilibrary -o docs
touch docs/.nojekyll
- uses: actions/upload-pages-artifact@v4
From b8c680a82fb1ad637746c3347cebf87a97635e0a Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:25:44 +0100
Subject: [PATCH 11/31] fixxes Docs Test
---
.github/workflows/docs.yml | 44 ++++++++++++++++++++++++++------------
1 file changed, 30 insertions(+), 14 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 2604705..9763edb 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,4 +1,4 @@
-name: website
+name: Documentation
on:
push:
@@ -8,45 +8,61 @@ on:
permissions:
contents: read
+ pages: write
+ id-token: write
jobs:
build:
runs-on: ubuntu-latest
+
steps:
- uses: actions/checkout@v6
- with:
- persist-credentials: false
+ # -----------------------------
+ # Setup Python
+ # -----------------------------
- uses: actions/setup-python@v6
with:
python-version: "3.14"
- - name: Install package
+ # -----------------------------
+ # Install package + dependencies
+ # -----------------------------
+ - name: Install project
run: |
python -m pip install --upgrade pip
pip install -e .
- - name: Install documentation tool
+ # -----------------------------
+ # Install documentation tool
+ # -----------------------------
+ - name: Install pdoc
run: pip install pdoc
+ # -----------------------------
+ # Build documentation
+ # IMPORTANT:
+ # src layout → use PYTHONPATH
+ # -----------------------------
- name: Build documentation
run: |
+ mkdir -p docs
PYTHONPATH=app/iLibrary/src pdoc ilibrary -o docs
touch docs/.nojekyll
- - uses: actions/upload-pages-artifact@v4
+ # -----------------------------
+ # Upload artifact for GitHub Pages
+ # -----------------------------
+ - uses: actions/upload-pages-artifact@v3
with:
path: docs/
+ # -----------------------------
+ # Deploy to GitHub Pages
+ # -----------------------------
deploy:
needs: build
runs-on: ubuntu-latest
- permissions:
- pages: write
- id-token: write
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
+
steps:
- - id: deployment
- uses: actions/deploy-pages@v4
\ No newline at end of file
+ - uses: actions/deploy-pages@v4
\ No newline at end of file
From 293b11baa07bc7e48d60c78234811faca8579504 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:27:49 +0100
Subject: [PATCH 12/31] fixxes Docs Test
---
.github/workflows/docs.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 9763edb..49c1ad5 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -46,8 +46,8 @@ jobs:
# -----------------------------
- name: Build documentation
run: |
- mkdir -p docs
- PYTHONPATH=app/iLibrary/src pdoc ilibrary -o docs
+ pip install pdoc
+ PYTHONPATH=app/iLibrary/src pdoc ilibrary --output-dir docs
touch docs/.nojekyll
# -----------------------------
From 07e2d0ed9061f261f87e2bdd9878e3afb1fcc53b Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:35:19 +0100
Subject: [PATCH 13/31] fixxes Docs Test
---
.github/workflows/docs.yml | 69 ++++++++++++++------------------------
docs/makedoc.py | 26 ++++++++++++++
2 files changed, 52 insertions(+), 43 deletions(-)
create mode 100644 docs/makedoc.py
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 49c1ad5..0b65655 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,68 +1,51 @@
-name: Documentation
+name: website
+# build the documentation whenever there are new commits on main
on:
push:
branches:
- main
- - Developer
+ # Alternative: only build for tags.
+ # tags:
+ # - '*'
+# security: restrict permissions for CI jobs.
permissions:
contents: read
- pages: write
- id-token: write
jobs:
+ # Build the documentation and upload the static HTML files as an artifact.
build:
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v6
-
- # -----------------------------
- # Setup Python
- # -----------------------------
+ with:
+ persist-credentials: false
- uses: actions/setup-python@v6
with:
- python-version: "3.14"
+ python-version: '3.14'
- # -----------------------------
- # Install package + dependencies
- # -----------------------------
- - name: Install project
- run: |
- python -m pip install --upgrade pip
- pip install -e .
+ # ADJUST THIS: install all dependencies (including pdoc)
+ - run: pip install -e .
+ # ADJUST THIS: build your documentation into docs/.
+ # We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here.
+ - run: python docs/make.py
- # -----------------------------
- # Install documentation tool
- # -----------------------------
- - name: Install pdoc
- run: pip install pdoc
-
- # -----------------------------
- # Build documentation
- # IMPORTANT:
- # src layout → use PYTHONPATH
- # -----------------------------
- - name: Build documentation
- run: |
- pip install pdoc
- PYTHONPATH=app/iLibrary/src pdoc ilibrary --output-dir docs
- touch docs/.nojekyll
-
- # -----------------------------
- # Upload artifact for GitHub Pages
- # -----------------------------
- - uses: actions/upload-pages-artifact@v3
+ - uses: actions/upload-pages-artifact@v4
with:
path: docs/
- # -----------------------------
- # Deploy to GitHub Pages
- # -----------------------------
+ # Deploy the artifact to GitHub pages.
+ # This is a separate job so that only actions/deploy-pages has the necessary permissions.
deploy:
needs: build
runs-on: ubuntu-latest
-
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
steps:
- - uses: actions/deploy-pages@v4
\ No newline at end of file
+ - id: deployment
+ uses: actions/deploy-pages@v4
\ No newline at end of file
diff --git a/docs/makedoc.py b/docs/makedoc.py
new file mode 100644
index 0000000..f7429c3
--- /dev/null
+++ b/docs/makedoc.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+from pathlib import Path
+import shutil
+from pdoc import pdoc, render
+
+# 1. Define your paths
+here = Path(__file__).parent
+# Change "api" to whatever folder name you want in MkDocs
+out = here / "docs" / "reference"
+# Change "my_package" to the name of your folder containing .py files
+src_folder = "../app/iLibrary/src"
+
+if out.exists():
+ shutil.rmtree(out)
+
+# 2. Configure (Optional)
+# If you don't have a 'pdoc-template' folder, comment this line out
+# render.configure(template_directory=here / "pdoc-template")
+
+# 3. Generate for YOUR project
+# Replace "your_project_name" with your actual package/module name
+pdoc(src_folder, output_directory=out)
+
+# # 4. Rename for MkDocs
+# for f in out.glob("**/*.html"):
+# f.rename(f.with_suffix(".md"))
\ No newline at end of file
From 185ccb73ae441b6bcb8e34f6bcf57b48be55905a Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:35:44 +0100
Subject: [PATCH 14/31] fixxes Docs Test
---
.github/workflows/docs.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 0b65655..73dc281 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -5,6 +5,7 @@ on:
push:
branches:
- main
+ - developer
# Alternative: only build for tags.
# tags:
# - '*'
From 217d3153041b15e0136e75e7e6fdc4b2e0df27e8 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:36:28 +0100
Subject: [PATCH 15/31] fixxes Docs Test
---
.github/workflows/docs.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 73dc281..10e83d8 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- main
- - developer
+ - Developer
# Alternative: only build for tags.
# tags:
# - '*'
From a8ef9b5ac503e5afeb0a561d8302511e543a75ef Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:37:05 +0100
Subject: [PATCH 16/31] fixxes Docs Test
---
.github/workflows/docs.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 10e83d8..2e839ab 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -30,7 +30,7 @@ jobs:
- run: pip install -e .
# ADJUST THIS: build your documentation into docs/.
# We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here.
- - run: python docs/make.py
+ - run: python docs/makedoc.py
- uses: actions/upload-pages-artifact@v4
with:
From bad0a6a87e712a73be1aa976108bba687bd23afa Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:37:56 +0100
Subject: [PATCH 17/31] fixxes Docs Test
---
.github/workflows/docs.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 2e839ab..95614e7 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -28,6 +28,7 @@ jobs:
# ADJUST THIS: install all dependencies (including pdoc)
- run: pip install -e .
+ - run: pip install pdoc
# ADJUST THIS: build your documentation into docs/.
# We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here.
- run: python docs/makedoc.py
From 6d7faf1058acf34db985a7ace2f05013dc789d86 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:41:03 +0100
Subject: [PATCH 18/31] fixxes Docs Test
---
docs/makedoc.py | 47 +++++++++++++++++++++++++++++------------------
1 file changed, 29 insertions(+), 18 deletions(-)
diff --git a/docs/makedoc.py b/docs/makedoc.py
index f7429c3..927c1b4 100644
--- a/docs/makedoc.py
+++ b/docs/makedoc.py
@@ -1,26 +1,37 @@
#!/usr/bin/env python3
from pathlib import Path
import shutil
-from pdoc import pdoc, render
+from pdoc import pdoc
-# 1. Define your paths
-here = Path(__file__).parent
-# Change "api" to whatever folder name you want in MkDocs
-out = here / "docs" / "reference"
-# Change "my_package" to the name of your folder containing .py files
-src_folder = "../app/iLibrary/src"
+# 1. Define paths relative to this script
+# .parent points to the folder containing this script (e.g., your 'docs_scripts' folder)
+script_dir = Path(__file__).parent.resolve()
-if out.exists():
- shutil.rmtree(out)
+# Define the root of your GitHub repo (one level up from the script folder)
+root_path = script_dir.parent
-# 2. Configure (Optional)
-# If you don't have a 'pdoc-template' folder, comment this line out
-# render.configure(template_directory=here / "pdoc-template")
+# Path to your source code: root/src
+src_folder = root_path / "app" / "iLibrary" /"src"
-# 3. Generate for YOUR project
-# Replace "your_project_name" with your actual package/module name
-pdoc(src_folder, output_directory=out)
+# Path to the output: root/docs/reference
+out = root_path / "docs" / "reference"
-# # 4. Rename for MkDocs
-# for f in out.glob("**/*.html"):
-# f.rename(f.with_suffix(".md"))
\ No newline at end of file
+def generate_docs():
+ # Clean up old documentation
+ if out.exists():
+ print(f"Cleaning up old docs at: {out}")
+ shutil.rmtree(out)
+
+ print(f"Generating docs from: {src_folder}")
+ print(f"Outputting to: {out}")
+
+ # 2. Generate for your project
+ # pdoc will crawl the src_folder and generate HTML by default
+ pdoc(src_folder, output_directory=out)
+
+ # 3. Optional: Rename for MkDocs if you are using the MkDocs-Material logic
+ # for f in out.glob("**/*.html"):
+ # f.rename(f.with_suffix(".md"))
+
+if __name__ == "__main__":
+ generate_docs()
\ No newline at end of file
From 510320c9c3231a343a0584d0a5a8b02c9075a124 Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:49:11 +0100
Subject: [PATCH 19/31] fixxes Docs Test
---
.github/workflows/docs.yml | 33 ++++++++++++++++-----------------
1 file changed, 16 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 95614e7..224422a 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,44 +1,43 @@
name: website
-# build the documentation whenever there are new commits on main
on:
push:
branches:
- main
- Developer
- # Alternative: only build for tags.
- # tags:
- # - '*'
-# security: restrict permissions for CI jobs.
permissions:
contents: read
jobs:
- # Build the documentation and upload the static HTML files as an artifact.
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
+
- uses: actions/setup-python@v6
with:
- python-version: '3.14'
+ python-version: '3.12'
+
+ - name: Install dependencies
+ run: |
+ pip install -e .
+ pip install pdoc mkdocs-material
+ # Note: mkdocs-material is the standard theme, add any other plugins you use here.
+
+ - name: Generate API Reference
+ run: python docs/makedoc.py
- # ADJUST THIS: install all dependencies (including pdoc)
- - run: pip install -e .
- - run: pip install pdoc
- # ADJUST THIS: build your documentation into docs/.
- # We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here.
- - run: python docs/makedoc.py
+ - name: Build Static Website
+ run: mkdocs build
- - uses: actions/upload-pages-artifact@v4
+ - name: Upload Page Artifact
+ uses: actions/upload-pages-artifact@v4
with:
- path: docs/
+ path: site/
- # Deploy the artifact to GitHub pages.
- # This is a separate job so that only actions/deploy-pages has the necessary permissions.
deploy:
needs: build
runs-on: ubuntu-latest
From 7e6e06e5ec20e3e567c69f27fc06c5a468db581f Mon Sep 17 00:00:00 2001
From: Andreas Legner
Date: Sun, 8 Mar 2026 20:52:37 +0100
Subject: [PATCH 20/31] fixxes Docs Test
---
docs/makedoc.py | 4 +-
docs/reference/iLibrary/src.md | 242 +++
docs/reference/iLibrary/src/Library.md | 563 +++++++
docs/reference/iLibrary/src/User.md | 556 +++++++
.../iLibrary/src/getInfoForLibrary.md | 604 +++++++
.../iLibrary/src/getUserInfoForUser.md | 659 ++++++++
docs/reference/iLibrary/src/saveLibrary.md | 1466 +++++++++++++++++
docs/reference/iLibrary/src/sendMSG.md | 497 ++++++
docs/reference/index.md | 7 +
docs/reference/search.js | 46 +
10 files changed, 4643 insertions(+), 1 deletion(-)
create mode 100644 docs/reference/iLibrary/src.md
create mode 100644 docs/reference/iLibrary/src/Library.md
create mode 100644 docs/reference/iLibrary/src/User.md
create mode 100644 docs/reference/iLibrary/src/getInfoForLibrary.md
create mode 100644 docs/reference/iLibrary/src/getUserInfoForUser.md
create mode 100644 docs/reference/iLibrary/src/saveLibrary.md
create mode 100644 docs/reference/iLibrary/src/sendMSG.md
create mode 100644 docs/reference/index.md
create mode 100644 docs/reference/search.js
diff --git a/docs/makedoc.py b/docs/makedoc.py
index 927c1b4..9fc69b7 100644
--- a/docs/makedoc.py
+++ b/docs/makedoc.py
@@ -32,6 +32,8 @@ def generate_docs():
# 3. Optional: Rename for MkDocs if you are using the MkDocs-Material logic
# for f in out.glob("**/*.html"):
# f.rename(f.with_suffix(".md"))
-
+ # Rename .html to .md
+ for f in out.glob("**/*.html"):
+ f.rename(f.with_suffix(".md"))
if __name__ == "__main__":
generate_docs()
\ No newline at end of file
diff --git a/docs/reference/iLibrary/src.md b/docs/reference/iLibrary/src.md
new file mode 100644
index 0000000..e98eb43
--- /dev/null
+++ b/docs/reference/iLibrary/src.md
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
1
+1from os.path import join + 2import paramiko + 3import pyodbc + 4import json + 5from datetime import datetime, date + 6from decimal import Decimal + 7from .getInfoForLibrary import * + 8from .saveLibrary import * + 9 +10 +11 +12class Library(getInfoForLibrary, saveLibrary): +13 """ +14 A class to manage libraries and files on an IBM i system. +15 +16 It provides methods to connect to the system via pyodbc for SQL and +17 paramiko for SFTP transfers. +18 """ +19 +20 # ------------------------------------------------------ +21 # __init__ - initzialise the class +22 # ------------------------------------------------------ +23 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +24 """ +25 Initializes the class attributes for a database connection. +26 The actual connection is established in the __enter__ method. +27 +28 Args: +29 db_user (str): The user ID for the database connection. +30 db_password (str): The password for the database user. +31 db_host (str): The system/host name for the database connection. +32 db_driver (str): The ODBC driver to be used. +33 """ +34 self.db_user = db_user +35 self.db_host = db_host +36 self.db_driver = db_driver +37 self.db_password = db_password +38 +39 # ------------------------------------------------------ +40 # __enter__ - enter to the class +41 # ------------------------------------------------------ +42 def __enter__(self) -> 'Library': +43 """ +44 Establishes the database connection when entering a 'with' block. +45 """ +46 try: +47 conn_str = ( +48 f"DRIVER={self.db_driver};" +49 f"SYSTEM={self.db_host};" +50 f"UID={self.db_user};" +51 f"PWD={self.db_password};" +52 ) +53 self.conn = pyodbc.connect(conn_str, autocommit=True) +54 return self +55 except pyodbc.Error as ex: +56 sqlstate = ex.args[0] +57 print(f"Database connection failed with error: {sqlstate}") +58 raise +59 +60 # ------------------------------------------------------ +61 # __exit__ - leave the class +62 # ------------------------------------------------------ +63 def __exit__(self, exc_type, exc_val, exc_tb): +64 """ +65 Closes the database connection when exiting a 'with' block. +66 This method is called automatically, even if an error occurred. +67 """ +68 self.iclose() +69 +70 +71 # ------------------------------------------------------ +72 # iClose - close connection +73 # ------------------------------------------------------ +74 def iclose(self): +75 """ +76 A helper method to close the connection, also useful for manual closure. +77 """ +78 if self.conn and not self.conn.closed: +79 self.conn.close() +80 pass +
13class Library(getInfoForLibrary, saveLibrary): +14 """ +15 A class to manage libraries and files on an IBM i system. +16 +17 It provides methods to connect to the system via pyodbc for SQL and +18 paramiko for SFTP transfers. +19 """ +20 +21 # ------------------------------------------------------ +22 # __init__ - initzialise the class +23 # ------------------------------------------------------ +24 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +25 """ +26 Initializes the class attributes for a database connection. +27 The actual connection is established in the __enter__ method. +28 +29 Args: +30 db_user (str): The user ID for the database connection. +31 db_password (str): The password for the database user. +32 db_host (str): The system/host name for the database connection. +33 db_driver (str): The ODBC driver to be used. +34 """ +35 self.db_user = db_user +36 self.db_host = db_host +37 self.db_driver = db_driver +38 self.db_password = db_password +39 +40 # ------------------------------------------------------ +41 # __enter__ - enter to the class +42 # ------------------------------------------------------ +43 def __enter__(self) -> 'Library': +44 """ +45 Establishes the database connection when entering a 'with' block. +46 """ +47 try: +48 conn_str = ( +49 f"DRIVER={self.db_driver};" +50 f"SYSTEM={self.db_host};" +51 f"UID={self.db_user};" +52 f"PWD={self.db_password};" +53 ) +54 self.conn = pyodbc.connect(conn_str, autocommit=True) +55 return self +56 except pyodbc.Error as ex: +57 sqlstate = ex.args[0] +58 print(f"Database connection failed with error: {sqlstate}") +59 raise +60 +61 # ------------------------------------------------------ +62 # __exit__ - leave the class +63 # ------------------------------------------------------ +64 def __exit__(self, exc_type, exc_val, exc_tb): +65 """ +66 Closes the database connection when exiting a 'with' block. +67 This method is called automatically, even if an error occurred. +68 """ +69 self.iclose() +70 +71 +72 # ------------------------------------------------------ +73 # iClose - close connection +74 # ------------------------------------------------------ +75 def iclose(self): +76 """ +77 A helper method to close the connection, also useful for manual closure. +78 """ +79 if self.conn and not self.conn.closed: +80 self.conn.close() +81 pass +
A class to manage libraries and files on an IBM i system.
+ +It provides methods to connect to the system via pyodbc for SQL and +paramiko for SFTP transfers.
+24 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +25 """ +26 Initializes the class attributes for a database connection. +27 The actual connection is established in the __enter__ method. +28 +29 Args: +30 db_user (str): The user ID for the database connection. +31 db_password (str): The password for the database user. +32 db_host (str): The system/host name for the database connection. +33 db_driver (str): The ODBC driver to be used. +34 """ +35 self.db_user = db_user +36 self.db_host = db_host +37 self.db_driver = db_driver +38 self.db_password = db_password +
Initializes the class attributes for a database connection. +The actual connection is established in the __enter__ method.
+ +Args: + db_user (str): The user ID for the database connection. + db_password (str): The password for the database user. + db_host (str): The system/host name for the database connection. + db_driver (str): The ODBC driver to be used.
+75 def iclose(self): +76 """ +77 A helper method to close the connection, also useful for manual closure. +78 """ +79 if self.conn and not self.conn.closed: +80 self.conn.close() +81 pass +
A helper method to close the connection, also useful for manual closure.
+1from os.path import join + 2import paramiko + 3import pyodbc + 4import json + 5from datetime import datetime, date + 6from decimal import Decimal + 7from .getUserInfoForUser import * + 8from .sendMSG import * + 9 +10class User(getUserInfoForUser, sendMSG): +11 """ +12 A class to manage User on IBMi System +13 +14 It provides methods to connect to the system via pyodbc for SQL and +15 paramiko for SFTP transfers. +16 """ +17 +18 # ------------------------------------------------------ +19 # __init__ - initzialise the class +20 # ------------------------------------------------------ +21 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +22 """ +23 Initializes the class attributes for a database connection. +24 The actual connection is established in the __enter__ method. +25 +26 Args: +27 db_user (str): The user ID for the database connection. +28 db_password (str): The password for the database user. +29 db_host (str): The system/host name for the database connection. +30 db_driver (str): The ODBC driver to be used. +31 """ +32 self.db_user = db_user +33 self.db_host = db_host +34 self.db_driver = db_driver +35 self.db_password = db_password +36 +37 # ------------------------------------------------------ +38 # __enter__ - enter to the class +39 # ------------------------------------------------------ +40 def __enter__(self) -> 'User': +41 """ +42 Establishes the database connection when entering a 'with' block. +43 """ +44 try: +45 conn_str = ( +46 f"DRIVER={self.db_driver};" +47 f"SYSTEM={self.db_host};" +48 f"UID={self.db_user};" +49 f"PWD={self.db_password};" +50 ) +51 self.conn = pyodbc.connect(conn_str, autocommit=True) +52 return self +53 except pyodbc.Error as ex: +54 sqlstate = ex.args[0] +55 print(f"Database connection failed with error: {sqlstate}") +56 raise +57 +58 # ------------------------------------------------------ +59 # __exit__ - leave the class +60 # ------------------------------------------------------ +61 def __exit__(self, exc_type, exc_val, exc_tb): +62 """ +63 Closes the database connection when exiting a 'with' block. +64 This method is called automatically, even if an error occurred. +65 """ +66 self.iclose() +67 +68 # ------------------------------------------------------ +69 # iClose - close connection +70 # ------------------------------------------------------ +71 def iclose(self): +72 """ +73 A helper method to close the connection, also useful for manual closure. +74 """ +75 if self.conn and not self.conn.closed: +76 self.conn.close() +77 pass +
11class User(getUserInfoForUser, sendMSG): +12 """ +13 A class to manage User on IBMi System +14 +15 It provides methods to connect to the system via pyodbc for SQL and +16 paramiko for SFTP transfers. +17 """ +18 +19 # ------------------------------------------------------ +20 # __init__ - initzialise the class +21 # ------------------------------------------------------ +22 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +23 """ +24 Initializes the class attributes for a database connection. +25 The actual connection is established in the __enter__ method. +26 +27 Args: +28 db_user (str): The user ID for the database connection. +29 db_password (str): The password for the database user. +30 db_host (str): The system/host name for the database connection. +31 db_driver (str): The ODBC driver to be used. +32 """ +33 self.db_user = db_user +34 self.db_host = db_host +35 self.db_driver = db_driver +36 self.db_password = db_password +37 +38 # ------------------------------------------------------ +39 # __enter__ - enter to the class +40 # ------------------------------------------------------ +41 def __enter__(self) -> 'User': +42 """ +43 Establishes the database connection when entering a 'with' block. +44 """ +45 try: +46 conn_str = ( +47 f"DRIVER={self.db_driver};" +48 f"SYSTEM={self.db_host};" +49 f"UID={self.db_user};" +50 f"PWD={self.db_password};" +51 ) +52 self.conn = pyodbc.connect(conn_str, autocommit=True) +53 return self +54 except pyodbc.Error as ex: +55 sqlstate = ex.args[0] +56 print(f"Database connection failed with error: {sqlstate}") +57 raise +58 +59 # ------------------------------------------------------ +60 # __exit__ - leave the class +61 # ------------------------------------------------------ +62 def __exit__(self, exc_type, exc_val, exc_tb): +63 """ +64 Closes the database connection when exiting a 'with' block. +65 This method is called automatically, even if an error occurred. +66 """ +67 self.iclose() +68 +69 # ------------------------------------------------------ +70 # iClose - close connection +71 # ------------------------------------------------------ +72 def iclose(self): +73 """ +74 A helper method to close the connection, also useful for manual closure. +75 """ +76 if self.conn and not self.conn.closed: +77 self.conn.close() +78 pass +
A class to manage User on IBMi System
+ +It provides methods to connect to the system via pyodbc for SQL and +paramiko for SFTP transfers.
+22 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): +23 """ +24 Initializes the class attributes for a database connection. +25 The actual connection is established in the __enter__ method. +26 +27 Args: +28 db_user (str): The user ID for the database connection. +29 db_password (str): The password for the database user. +30 db_host (str): The system/host name for the database connection. +31 db_driver (str): The ODBC driver to be used. +32 """ +33 self.db_user = db_user +34 self.db_host = db_host +35 self.db_driver = db_driver +36 self.db_password = db_password +
Initializes the class attributes for a database connection. +The actual connection is established in the __enter__ method.
+ +Args: + db_user (str): The user ID for the database connection. + db_password (str): The password for the database user. + db_host (str): The system/host name for the database connection. + db_driver (str): The ODBC driver to be used.
+72 def iclose(self): +73 """ +74 A helper method to close the connection, also useful for manual closure. +75 """ +76 if self.conn and not self.conn.closed: +77 self.conn.close() +78 pass +
A helper method to close the connection, also useful for manual closure.
+1import json + 2from datetime import datetime, date + 3from decimal import Decimal + 4 + 5 + 6class getInfoForLibrary: + 7 def __init__(self, connection): + 8 self.conn = connection + 9 +10 def _convert_to_json_ready(self, row, description): +11 """Interne Hilfsmethode zur Typ-Konvertierung und Bereinigung.""" +12 row_dict = {} +13 titles = [col[0] for col in description] +14 +15 for i, value in enumerate(row): +16 key = titles[i] +17 # Typ-Prüfung für JSON-Serialisierung +18 if isinstance(value, (datetime, date)): +19 row_dict[key] = value.isoformat() +20 elif isinstance(value, Decimal): +21 row_dict[key] = float(value) +22 elif isinstance(value, bytes): +23 row_dict[key] = value.decode('utf-8', errors='replace') +24 elif value is None: +25 row_dict[key] = None +26 else: +27 # Entfernt unnötige Leerzeichen von CHAR-Feldern +28 row_dict[key] = str(value).strip() +29 return row_dict +30 +31 def getLibraryInfo(self, library: str, wantJson=True): +32 if not library or len(library) > 10: +33 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") +34 +35 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" +36 try: +37 with self.conn.cursor() as cursor: +38 cursor.execute(sql_query) +39 row = cursor.fetchone() +40 +41 if not row: +42 error_msg = {"error": f"No data found for library: {library}"} +43 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) +44 +45 if wantJson: +46 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) +47 +48 return row +49 except Exception as e: +50 print(f"Fehler bei getLibraryInfo: {e}") +51 return None +52 +53 def getFileInfo(self, library: str, qFiles: bool = False) -> str: +54 if not library: +55 return json.dumps([{"error": "A library name is required."}]) +56 +57 if qFiles: +58 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" +59 else: +60 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" +61 +62 try: +63 with self.conn.cursor() as cursor: +64 cursor.execute(sql) +65 rows = cursor.fetchall() +66 +67 if not rows: +68 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) +69 +70 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +71 self.conn.commit() +72 return json.dumps(result_list, indent=4) +73 except Exception as e: +74 if self.conn: self.conn.rollback() +75 return json.dumps([{"error": f"Database Error: {str(e)}"}]) +76 +77 def getAllLibraries(self): +78 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste +79 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" +80 try: +81 with self.conn.cursor() as cursor: +82 cursor.execute(sql) +83 rows = cursor.fetchall() +84 +85 if not rows: +86 return json.dumps([{"error": "No Libraries found"}]) +87 +88 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +89 self.conn.commit() +90 return json.dumps(result_list, indent=4) +91 except Exception as e: +92 print(f"Fehler bei getAllLibraries: {e}") +93 if self.conn: self.conn.rollback() +94 return False +
7class getInfoForLibrary: + 8 def __init__(self, connection): + 9 self.conn = connection +10 +11 def _convert_to_json_ready(self, row, description): +12 """Interne Hilfsmethode zur Typ-Konvertierung und Bereinigung.""" +13 row_dict = {} +14 titles = [col[0] for col in description] +15 +16 for i, value in enumerate(row): +17 key = titles[i] +18 # Typ-Prüfung für JSON-Serialisierung +19 if isinstance(value, (datetime, date)): +20 row_dict[key] = value.isoformat() +21 elif isinstance(value, Decimal): +22 row_dict[key] = float(value) +23 elif isinstance(value, bytes): +24 row_dict[key] = value.decode('utf-8', errors='replace') +25 elif value is None: +26 row_dict[key] = None +27 else: +28 # Entfernt unnötige Leerzeichen von CHAR-Feldern +29 row_dict[key] = str(value).strip() +30 return row_dict +31 +32 def getLibraryInfo(self, library: str, wantJson=True): +33 if not library or len(library) > 10: +34 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") +35 +36 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" +37 try: +38 with self.conn.cursor() as cursor: +39 cursor.execute(sql_query) +40 row = cursor.fetchone() +41 +42 if not row: +43 error_msg = {"error": f"No data found for library: {library}"} +44 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) +45 +46 if wantJson: +47 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) +48 +49 return row +50 except Exception as e: +51 print(f"Fehler bei getLibraryInfo: {e}") +52 return None +53 +54 def getFileInfo(self, library: str, qFiles: bool = False) -> str: +55 if not library: +56 return json.dumps([{"error": "A library name is required."}]) +57 +58 if qFiles: +59 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" +60 else: +61 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" +62 +63 try: +64 with self.conn.cursor() as cursor: +65 cursor.execute(sql) +66 rows = cursor.fetchall() +67 +68 if not rows: +69 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) +70 +71 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +72 self.conn.commit() +73 return json.dumps(result_list, indent=4) +74 except Exception as e: +75 if self.conn: self.conn.rollback() +76 return json.dumps([{"error": f"Database Error: {str(e)}"}]) +77 +78 def getAllLibraries(self): +79 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste +80 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" +81 try: +82 with self.conn.cursor() as cursor: +83 cursor.execute(sql) +84 rows = cursor.fetchall() +85 +86 if not rows: +87 return json.dumps([{"error": "No Libraries found"}]) +88 +89 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +90 self.conn.commit() +91 return json.dumps(result_list, indent=4) +92 except Exception as e: +93 print(f"Fehler bei getAllLibraries: {e}") +94 if self.conn: self.conn.rollback() +95 return False +
32 def getLibraryInfo(self, library: str, wantJson=True): +33 if not library or len(library) > 10: +34 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") +35 +36 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" +37 try: +38 with self.conn.cursor() as cursor: +39 cursor.execute(sql_query) +40 row = cursor.fetchone() +41 +42 if not row: +43 error_msg = {"error": f"No data found for library: {library}"} +44 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) +45 +46 if wantJson: +47 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) +48 +49 return row +50 except Exception as e: +51 print(f"Fehler bei getLibraryInfo: {e}") +52 return None +
54 def getFileInfo(self, library: str, qFiles: bool = False) -> str: +55 if not library: +56 return json.dumps([{"error": "A library name is required."}]) +57 +58 if qFiles: +59 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" +60 else: +61 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" +62 +63 try: +64 with self.conn.cursor() as cursor: +65 cursor.execute(sql) +66 rows = cursor.fetchall() +67 +68 if not rows: +69 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) +70 +71 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +72 self.conn.commit() +73 return json.dumps(result_list, indent=4) +74 except Exception as e: +75 if self.conn: self.conn.rollback() +76 return json.dumps([{"error": f"Database Error: {str(e)}"}]) +
78 def getAllLibraries(self): +79 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste +80 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" +81 try: +82 with self.conn.cursor() as cursor: +83 cursor.execute(sql) +84 rows = cursor.fetchall() +85 +86 if not rows: +87 return json.dumps([{"error": "No Libraries found"}]) +88 +89 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] +90 self.conn.commit() +91 return json.dumps(result_list, indent=4) +92 except Exception as e: +93 print(f"Fehler bei getAllLibraries: {e}") +94 if self.conn: self.conn.rollback() +95 return False +
1from os.path import join + 2import paramiko + 3import pyodbc + 4import json + 5from datetime import datetime, date + 6from decimal import Decimal + 7 + 8class getUserInfoForUser(): + 9 """ + 10 Handles user information retrieval and messaging functionalities. + 11 + 12 This class provides methods to interact with the database for retrieving user information + 13 and to send messages to specified users. It supports data retrieval in different formats + 14 (e.g., JSON or tuple), and it enables system messaging with configurable options. + 15 + 16 :ivar conn: Database connection object used for executing queries. + 17 :type conn: Any + 18 """ + 19 def getAllUsers(self, wantJson: bool = False): + 20 """ + 21 Retrieves all user information from the database. Optionally returns the data in + 22 JSON format depending on the provided parameter. + 23 + 24 Retrieves a list of users stored in the database and can output the data either as + 25 a list of tuples or in JSON format. The query fetches all fields available in the + 26 user information database table and handles cases where no data is found. + 27 + 28 :param wantJson: Boolean flag to indicate whether the result should be returned + 29 in JSON format. If set to False, the result will be a list of tuples. Default + 30 is False. + 31 :return: The data fetched from the database. When `wantJson` is True, returns a + 32 JSON object as a string. Otherwise, returns a list of tuples. + 33 """ + 34 sql_query = "SELECT * FROM qsys2.user_info" + 35 + 36 def json_serial(obj): + 37 if hasattr(obj, 'isoformat'): + 38 return obj.isoformat() + 39 return str(obj) + 40 + 41 try: + 42 with self.conn.cursor() as cursor: + 43 cursor.execute(sql_query) + 44 rows = cursor.fetchall() + 45 + 46 if not rows: + 47 error_msg = {'error': 'No data found'} + 48 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] + 49 + 50 # Get column names + 51 columns = [column[0] for column in cursor.description] + 52 + 53 if wantJson: + 54 # Create a LIST of dictionaries + 55 results = [dict(zip(columns, r)) for r in rows] + 56 return json.dumps(results, indent=4, default=json_serial) + 57 + 58 return rows # Returns the list of tuples + 59 + 60 except Exception as e: + 61 print(f"An error occurred: {e}") + 62 return None + 63 + 64 def getSingleUserInformation(self, username: str, wantJson: bool = False): + 65 """ + 66 Retrieves information about a specific user from the database based on their username. The function supports + 67 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. + 68 + 69 :param username: The username of the database user whose information is to be retrieved. Must not be empty. + 70 :type username: str + 71 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. + 72 :type wantJson: bool + 73 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. + 74 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the + 75 value of `wantJson`. Returns None if an exception occurs. + 76 :rtype: Union[tuple, str, None] + 77 :raises ValueError: If the `username` input is empty or None. + 78 """ + 79 if not username: + 80 raise ValueError("A username is required.") + 81 + 82 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" + 83 + 84 def json_serial(obj): + 85 # Handle datetime and Decimal (common in DB2) + 86 if hasattr(obj, 'isoformat'): + 87 return obj.isoformat() + 88 return str(obj) + 89 + 90 try: + 91 with self.conn.cursor() as cursor: + 92 cursor.execute(sql_query) + 93 row = cursor.fetchone() # Since you only expect one user + 94 + 95 if not row: + 96 error_msg = {'error': 'No data found for User: ' + username} + 97 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) + 98 + 99 # DYNAMICALLY get column names from the database itself +100 columns = [column[0] for column in cursor.description] +101 row_dict = dict(zip(columns, row)) +102 +103 if wantJson: +104 return json.dumps(row_dict, indent=4, default=json_serial) +105 return row # Returns the tuple +106 +107 except Exception as e: +108 print(f"An error occurred: {e}") +109 return None +
9class getUserInfoForUser(): + 10 """ + 11 Handles user information retrieval and messaging functionalities. + 12 + 13 This class provides methods to interact with the database for retrieving user information + 14 and to send messages to specified users. It supports data retrieval in different formats + 15 (e.g., JSON or tuple), and it enables system messaging with configurable options. + 16 + 17 :ivar conn: Database connection object used for executing queries. + 18 :type conn: Any + 19 """ + 20 def getAllUsers(self, wantJson: bool = False): + 21 """ + 22 Retrieves all user information from the database. Optionally returns the data in + 23 JSON format depending on the provided parameter. + 24 + 25 Retrieves a list of users stored in the database and can output the data either as + 26 a list of tuples or in JSON format. The query fetches all fields available in the + 27 user information database table and handles cases where no data is found. + 28 + 29 :param wantJson: Boolean flag to indicate whether the result should be returned + 30 in JSON format. If set to False, the result will be a list of tuples. Default + 31 is False. + 32 :return: The data fetched from the database. When `wantJson` is True, returns a + 33 JSON object as a string. Otherwise, returns a list of tuples. + 34 """ + 35 sql_query = "SELECT * FROM qsys2.user_info" + 36 + 37 def json_serial(obj): + 38 if hasattr(obj, 'isoformat'): + 39 return obj.isoformat() + 40 return str(obj) + 41 + 42 try: + 43 with self.conn.cursor() as cursor: + 44 cursor.execute(sql_query) + 45 rows = cursor.fetchall() + 46 + 47 if not rows: + 48 error_msg = {'error': 'No data found'} + 49 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] + 50 + 51 # Get column names + 52 columns = [column[0] for column in cursor.description] + 53 + 54 if wantJson: + 55 # Create a LIST of dictionaries + 56 results = [dict(zip(columns, r)) for r in rows] + 57 return json.dumps(results, indent=4, default=json_serial) + 58 + 59 return rows # Returns the list of tuples + 60 + 61 except Exception as e: + 62 print(f"An error occurred: {e}") + 63 return None + 64 + 65 def getSingleUserInformation(self, username: str, wantJson: bool = False): + 66 """ + 67 Retrieves information about a specific user from the database based on their username. The function supports + 68 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. + 69 + 70 :param username: The username of the database user whose information is to be retrieved. Must not be empty. + 71 :type username: str + 72 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. + 73 :type wantJson: bool + 74 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. + 75 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the + 76 value of `wantJson`. Returns None if an exception occurs. + 77 :rtype: Union[tuple, str, None] + 78 :raises ValueError: If the `username` input is empty or None. + 79 """ + 80 if not username: + 81 raise ValueError("A username is required.") + 82 + 83 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" + 84 + 85 def json_serial(obj): + 86 # Handle datetime and Decimal (common in DB2) + 87 if hasattr(obj, 'isoformat'): + 88 return obj.isoformat() + 89 return str(obj) + 90 + 91 try: + 92 with self.conn.cursor() as cursor: + 93 cursor.execute(sql_query) + 94 row = cursor.fetchone() # Since you only expect one user + 95 + 96 if not row: + 97 error_msg = {'error': 'No data found for User: ' + username} + 98 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) + 99 +100 # DYNAMICALLY get column names from the database itself +101 columns = [column[0] for column in cursor.description] +102 row_dict = dict(zip(columns, row)) +103 +104 if wantJson: +105 return json.dumps(row_dict, indent=4, default=json_serial) +106 return row # Returns the tuple +107 +108 except Exception as e: +109 print(f"An error occurred: {e}") +110 return None +
Handles user information retrieval and messaging functionalities.
+ +This class provides methods to interact with the database for retrieving user information +and to send messages to specified users. It supports data retrieval in different formats +(e.g., JSON or tuple), and it enables system messaging with configurable options.
+ +:ivar conn: Database connection object used for executing queries.
+20 def getAllUsers(self, wantJson: bool = False): +21 """ +22 Retrieves all user information from the database. Optionally returns the data in +23 JSON format depending on the provided parameter. +24 +25 Retrieves a list of users stored in the database and can output the data either as +26 a list of tuples or in JSON format. The query fetches all fields available in the +27 user information database table and handles cases where no data is found. +28 +29 :param wantJson: Boolean flag to indicate whether the result should be returned +30 in JSON format. If set to False, the result will be a list of tuples. Default +31 is False. +32 :return: The data fetched from the database. When `wantJson` is True, returns a +33 JSON object as a string. Otherwise, returns a list of tuples. +34 """ +35 sql_query = "SELECT * FROM qsys2.user_info" +36 +37 def json_serial(obj): +38 if hasattr(obj, 'isoformat'): +39 return obj.isoformat() +40 return str(obj) +41 +42 try: +43 with self.conn.cursor() as cursor: +44 cursor.execute(sql_query) +45 rows = cursor.fetchall() +46 +47 if not rows: +48 error_msg = {'error': 'No data found'} +49 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] +50 +51 # Get column names +52 columns = [column[0] for column in cursor.description] +53 +54 if wantJson: +55 # Create a LIST of dictionaries +56 results = [dict(zip(columns, r)) for r in rows] +57 return json.dumps(results, indent=4, default=json_serial) +58 +59 return rows # Returns the list of tuples +60 +61 except Exception as e: +62 print(f"An error occurred: {e}") +63 return None +
Retrieves all user information from the database. Optionally returns the data in +JSON format depending on the provided parameter.
+ +Retrieves a list of users stored in the database and can output the data either as +a list of tuples or in JSON format. The query fetches all fields available in the +user information database table and handles cases where no data is found.
+ +++The data fetched from the database. When
+wantJsonis True, returns a + JSON object as a string. Otherwise, returns a list of tuples.
65 def getSingleUserInformation(self, username: str, wantJson: bool = False): + 66 """ + 67 Retrieves information about a specific user from the database based on their username. The function supports + 68 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. + 69 + 70 :param username: The username of the database user whose information is to be retrieved. Must not be empty. + 71 :type username: str + 72 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. + 73 :type wantJson: bool + 74 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. + 75 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the + 76 value of `wantJson`. Returns None if an exception occurs. + 77 :rtype: Union[tuple, str, None] + 78 :raises ValueError: If the `username` input is empty or None. + 79 """ + 80 if not username: + 81 raise ValueError("A username is required.") + 82 + 83 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" + 84 + 85 def json_serial(obj): + 86 # Handle datetime and Decimal (common in DB2) + 87 if hasattr(obj, 'isoformat'): + 88 return obj.isoformat() + 89 return str(obj) + 90 + 91 try: + 92 with self.conn.cursor() as cursor: + 93 cursor.execute(sql_query) + 94 row = cursor.fetchone() # Since you only expect one user + 95 + 96 if not row: + 97 error_msg = {'error': 'No data found for User: ' + username} + 98 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) + 99 +100 # DYNAMICALLY get column names from the database itself +101 columns = [column[0] for column in cursor.description] +102 row_dict = dict(zip(columns, row)) +103 +104 if wantJson: +105 return json.dumps(row_dict, indent=4, default=json_serial) +106 return row # Returns the tuple +107 +108 except Exception as e: +109 print(f"An error occurred: {e}") +110 return None +
Retrieves information about a specific user from the database based on their username. The function supports +returning data either as a JSON-formatted string or as a tuple with corresponding database fields.
+ +++ +A tuple containing database fields if
+wantJsonis False, or a JSON-formatted string ifwantJsonis True. + If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the + value ofwantJson. Returns None if an exception occurs.
username input is empty or None.1from _ast import Raise + 2from os.path import join + 3import paramiko + 4import pyodbc + 5import json + 6from datetime import datetime, date + 7from decimal import Decimal + 8from typing import Union + 9from pathlib import PureWindowsPath + 10 + 11 + 12class saveLibrary: + 13 + 14 def saveLibrary(self, + 15 library: str, + 16 saveFileName: str, + 17 dev: str = None, + 18 vol: str = None, + 19 toLibrary: str = None, + 20 description: str = None, + 21 localPath: str = None, + 22 remPath: str = None, + 23 getZip: bool = False, + 24 port: int = None, + 25 remSavf=True, + 26 version: str = None, + 27 max_records: Union[int, str, None] = None, + 28 asp: Union[int, str, None] = None, + 29 waitFile: Union[int, str, None] = None, + 30 share: str = None, + 31 authority: str = None + 32 ) -> bool: + 33 """ + 34 Saves a library to a specified save file, providing options for further customization such + 35 as setting the target release, saving as a zip file, specifying the device, volume, and more. + 36 + 37 :param library: The name of the library to be saved. Must be a valid library name or one of + 38 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. + 39 :type library: str + 40 :param saveFileName: The name of the save file where the library will be saved. + 41 :type saveFileName: str + 42 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. + 43 :type dev: str, optional + 44 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. + 45 :type vol: str, optional + 46 :param toLibrary: Target library where the save file will be temporarily stored. Defaults + 47 to the value of `library` if not specified. + 48 :type toLibrary: str, optional + 49 :param description: An optional description for the save file to be created. + 50 :type description: str, optional + 51 :param localPath: The local path where the save file will be downloaded if `getZip` is set + 52 to True. Must be an absolute path. + 53 :type localPath: str, optional + 54 :param remPath: The remote directory path on the target system to temporarily store the + 55 save file if `getZip` is set to True. Must be an absolute path. + 56 :type remPath: str, optional + 57 :param getZip: A flag that determines whether the save file should be archived into a zip + 58 file and downloaded locally. + 59 :type getZip: bool + 60 :param port: Specifies the port to be used for transferring the save file when `getZip` is + 61 enabled. + 62 :type port: int, optional + 63 :param remSavf: A flag indicating whether the save file should be removed from the remote + 64 target system after a successful save. + 65 :type remSavf: bool + 66 :param version: The target release version for the save operation. Valid values include + 67 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. + 68 :type version: str, optional + 69 :param max_records: Optional parameter for specifying the maximum number of records in + 70 the save file. + 71 :type max_records: Union[int, str, None], optional + 72 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. + 73 :type asp: Union[int, str, None], optional + 74 :param waitFile: The amount of time to wait for file access locks to be released. + 75 :type waitFile: Union[int, str, None], optional + 76 :param share: Specifies the share handling for threads or users accessing the save file. + 77 :type share: str, optional + 78 :param authority: Authority option to set for the save file being saved. + 79 :type authority: str, optional + 80 :return: A boolean indicating whether the library was successfully saved. Returns True on + 81 success or False on failure. + 82 :rtype: bool + 83 """ + 84 # Target Release List + 85 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", + 86 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", + 87 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", + 88 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", + 89 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] + 90 + 91 # check if something missing from the Arguments + 92 # check if Library is empty or not + 93 if not library: + 94 raise ValueError("A library name is required.") + 95 # check if saveFileName is empty or not + 96 if not saveFileName: + 97 raise ValueError("A save file name is required.") + 98 # check if toLibrary is empty or not + 99 if not toLibrary: +100 toLibrary = library +101 # check if user want the SaveFile as ZIP File +102 if getZip: +103 if not remPath: +104 raise ValueError("A remote path is required. Use 'remPath' instead.") +105 elif remPath[-1] == '/': +106 remPath = remPath[:-1] +107 if not localPath: +108 raise ValueError("A local path is required. Use 'localPath' instead.") +109 elif localPath[-1] == '/': +110 localPath = localPath[:-1] +111 # check wich Version of SaveFile is wanted +112 if not version in list(trgList): +113 version = "*CURRENT" +114 else: +115 version = version.upper() +116 command_str: str = f'SAVLIB' +117 +118 # check if Library is valid or not +119 validated_library = self.__validate_max_value(value=library, param_name='library', +120 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', +121 library]) +122 if validated_library: +123 command_str += f' LIB({validated_library})' +124 else: +125 library_str = str(library) +126 raise ValueError( +127 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") +128 # check Dev - Device +129 if not dev in ['*SAVF', '*MEDDFN']: +130 command_str += f' DEV(*SAVF)' +131 else: +132 command_str += f' DEV({dev.upper()})' +133 if vol is not None and vol == '*MOUNTED': +134 command_str += f' VOL({vol})' +135 # starting with mem main Sourcecode of saveLLibrary +136 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, +137 share=share, authority=authority): +138 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +139 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +140 #print(command_str) +141 try: +142 with self.conn.cursor() as cursor: +143 # execute the Command for creating a Savefile. +144 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +145 if getZip: +146 try: +147 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') +148 +149 destination_local_path = join(localPath, saveFileName.upper() + '.savf') +150 command_str = ( +151 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " +152 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" +153 ) +154 +155 # Execute the command on the remote system +156 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) +157 +158 if self.__getSavFile(localFilePath=destination_local_path, +159 remotePath=remote_temp_savf_path, port=port): +160 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" +161 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) +162 else: +163 raise ValueError("Something went wrong. With downloading the Save File.") +164 if remSavf: +165 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): +166 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") +167 +168 except Exception as e: +169 self.__handle_error(error=e, pgm="saveLibrary - Transfer") +170 +171 except Exception as e: +172 self.__handle_error(error=e, pgm="saveLibrary") +173 self.conn.rollback() +174 return False +175 else: +176 self.conn.commit() +177 if getZip: +178 print(f"File successfully downloaded to: {destination_local_path}") +179 return True +180 +181 print(f"Successfully saved in the Library '{library}' successfully.") +182 return True +183 +184 return False +185 +186 # ------------------------------------------------------ +187 # sub Function: create the Savefile on the AS400 +188 # ------------------------------------------------------ +189 def __crtsavf(self, +190 saveFileName: str, +191 library: str, +192 description: str = None, +193 max_records: Union[int, str, None] = None, +194 asp: Union[int, str, None] = None, +195 waitFile: Union[int, str, None] = None, +196 share: str = None, +197 authority: str = None +198 ) -> bool: +199 """ +200 Sub-function to create a save file on the IBM i server. +201 +202 This function executes the `CRTSAVF` (Create Save File) CL command +203 to create a new save file in the specified library. This is a +204 prerequisite for saving a library's contents. +205 +206 Args: +207 saveFileName (str): The name of the save file to be created. +208 This will be the AS/400 object name. +209 library (str): The name of the library where the save file will be created. +210 description (str, optional): A text description for the save file. Defaults to None. +211 +212 Returns: +213 bool: True if the save file was created successfully, False otherwise. +214 """ +215 # check is a parameter empty or not +216 +217 if not saveFileName: +218 raise ValueError("A file name is required.") +219 if not library: +220 raise ValueError("A library name is required.") +221 if not description: +222 description = 'A SaveFile from iLibrary' +223 +224 command_str: str = f"CRTSAVF FILE({library.upper().strip()}/{saveFileName.upper().strip()}) TEXT('{description.strip()}')" +225 +226 # check max_records for MAXRCDS parameter +227 if self.__validate_max_value(value=max_records, param_name='max_records', str_format=['*NOMAX'], +228 max_limit=4293525600) and not None: +229 command_str += f" MAXRCDS({max_records})" +230 # check asp for ASP 2147483647 +231 if self.__validate_max_value(value=asp, param_name='asp', str_format=['*LIBASP'], max_limit=32) and not None: +232 command_str += f" ASP({asp})" +233 if self.__validate_max_value(value=waitFile, param_name='waitFile', str_format=['*IMMED', '*CLS'], +234 max_limit=2147483647) and not None: +235 command_str += f" WAITFILE({waitFile})" +236 if self.__validate_max_value(value=share, param_name='share', str_format=['*YES', '*NO']) and not None: +237 command_str += f" SHARE({share})" +238 +239 if authority is not None: +240 upper_authority = authority.upper() +241 +242 # 1. Check for custom authority (not in list AND up to 10 chars) +243 if upper_authority not in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE'] and len( +244 upper_authority) <= 10: +245 # **CORRECTION 1: Use upper_authority here, not the undefined 'auth'** +246 command_str += f" AUT({upper_authority})" +247 # The 'pass' statements are redundant and can be removed +248 +249 # 2. Add an 'elif' to handle the case where it IS one of the standard values +250 elif upper_authority in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE']: +251 command_str += f" AUT({upper_authority})" +252 +253 +254 try: +255 with self.conn.cursor() as cursor: +256 # execute the Command for creating a Savefile. +257 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +258 +259 except Exception as e: +260 self.__handle_error(error=e, pgm="__crtsavf") +261 # remove a SAVF if its exists and we got an error +262 if e.args[0] == 'HY000': +263 sql = """ +264 SELECT 1 +265 FROM QSYS2.SAVE_FILE_INFO +266 WHERE SAVE_FILE_LIBRARY = ? \ +267 AND SAVE_FILE = ? +268 FETCH FIRST 1 ROW ONLY \ +269 """ +270 cursor = self.conn.cursor() +271 cursor.execute(sql, library, saveFileName) +272 result = cursor.fetchone() +273 if result is not None: +274 self.removeFile(library=library, saveFileName=saveFileName) +275 self.conn.rollback() +276 raise ValueError(e) +277 else: +278 self.conn.commit() +279 return True +280 +281 # -------------------------------------------------------------------------- +282 # __validate_max_value - Helper Function for checking parameter +283 # -------------------------------------------------------------------------- +284 def __validate_max_value(self, +285 value: Union[int, str, None], +286 param_name: str, +287 str_format: list[str], +288 min_limit: int = 1, +289 max_limit: int = None +290 ) -> Union[int, str, bool]: # Includes bool as requested +291 """ +292 Validates an input value for 'MAX' type parameters against a custom range. +293 Handles special strings defined in str_format and numeric values. +294 +295 Returns: The validated integer, the standardized special string, or False on failure (if no exception is raised). +296 Raises: ValueError for invalid string format or out-of-range number. +297 """ +298 +299 # Helper for clear error messages +300 str_options = ", ".join([f"'{s}'" for s in str_format]) +301 +302 # 1. Handle special string +303 if isinstance(value, str): +304 upper_value = value.upper() +305 +306 for special_value in str_format: +307 normalized_special_value = special_value.upper() +308 +309 if upper_value == special_value.upper() or upper_value == normalized_special_value: +310 # Found a match! Return the official, fully formatted string. +311 return special_value +312 +313 # 2. Attempt Numeric Conversion (handles int and string-of-int) +314 if value is not None: +315 try: +316 numeric_value = int(value) +317 except ValueError: +318 # Value is an invalid string (e.g., 'hello') +319 raise ValueError( +320 f"Invalid value for {param_name}. Must be '{str_format}' or a number " +321 f"between {min_limit} and {max_limit:,}." +322 ) +323 else: +324 # If the value is None +325 return False +326 +327 # 3. Check Numeric Range +328 if min_limit <= numeric_value <= max_limit: +329 return numeric_value +330 else: +331 # Number is out of range +332 raise ValueError( +333 f"Invalid numeric value for {param_name}. Must be between {min_limit} and {max_limit:,}. " +334 f"Received: {numeric_value}" +335 ) +336 +337 # ------------------------------------------------------ +338 # getZipFile - getting the Zipfile from the SaveFile +339 # ------------------------------------------------------ +340 def __getSavFile(self, +341 localFilePath: str, +342 remotePath: str, +343 port: int = None +344 ) -> bool: +345 """ +346 Downloads a file from the remote IBM i via SFTP. +347 +348 This method uses Paramiko to establish a secure shell (SSH) connection and +349 then an SFTP session to transfer a file from a specified remote location +350 on the IBM i's IFS to a local path. +351 +352 Args: +353 localFilePath (str): The full path to the file on the remote IBM i's IFS. +354 remotePath (str): The full path on the local machine where the file +355 will be saved. For example, '/Users/user/Documents/somefile.savf'. +356 port (int, optional): The port to connect to the IBMi server. Defaults to None. +357 +358 Returns: +359 bool: True if the file was downloaded successfully, False otherwise. +360 +361 Raises: +362 ValueError: If either the remote_file_path or local_save_path is not provided. +363 """ +364 if not localFilePath: +365 print("Error: A local file path is required.") +366 return False +367 if not remotePath: +368 print("Error: A remote path is required.") +369 return False +370 if not port: +371 port = 2222 +372 +373 remotePath = PureWindowsPath(remotePath).as_posix() +374 ssh_client = paramiko.SSHClient() +375 +376 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +377 +378 try: +379 with ssh_client: +380 ssh_client.connect( +381 hostname=self.db_host, +382 username=self.db_user, +383 password=self.db_password, +384 port=port +385 ) +386 with ssh_client.open_sftp() as ftp_client: +387 ftp_client.get(remotePath, localFilePath) +388 return True +389 +390 except paramiko.ssh_exception.AuthenticationException as e: +391 print(f"Authentication failed. Check your username and password: {e}") +392 return False +393 except paramiko.ssh_exception.SSHException as e: +394 print(f"SSH error occurred: {e}") +395 return False +396 except FileNotFoundError as e: +397 print(f"File not found on the remote host: {e}") +398 return False +399 +400 finally: +401 pass +402 +403 def removeFile(self, library: str, saveFileName: str) -> bool: +404 """ +405 Removes a save file from the specified library. +406 +407 This function executes the system command to delete a save file from an IBM i +408 system. It connects to the database through a cursor, and attempts to perform +409 the operation. If an error is encountered during execution, the function +410 rolls back the transaction and logs the error. On success, the transaction +411 is committed. +412 +413 :param library: The name of the library containing the save file to be removed. +414 :type library: str +415 :param saveFileName: The name of the save file to be removed. +416 :type saveFileName: str +417 :return: True if the save file is removed successfully, otherwise False. +418 :rtype: bool +419 """ +420 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" +421 try: +422 with self.conn.cursor() as cursor: +423 # execute the Command for deleting a Savefile. +424 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +425 +426 except Exception as e: +427 self.__handle_error(error=e, pgm="removeFile") +428 self.conn.rollback() +429 return False +430 else: +431 self.conn.commit() +432 return True +433 +434 def __handle_error(self, error, pgm: str): +435 """ +436 Handles errors encountered during the execution of a command. +437 +438 This method processes an error raised during the execution of a command in a +439 specific function and extracts detailed error information including SQLSTATE +440 and the error message. The formatted details are printed to the console for +441 debugging purposes. +442 +443 :param error: The error object encountered during command execution. +444 :type error: Exception +445 :param pgm: The name of the function where the error occurred. +446 :type pgm: str +447 :return: None +448 """ +449 print("-------------------------------------------------------------") +450 print(f"An error occurred while executing command in function {pgm}:") +451 sqlstate = error.args[0] +452 error_message = error.args[1] +453 +454 print(f"SQLSTATE: {sqlstate}") +455 print(f"Message: {error_message}") +
13class saveLibrary: + 14 + 15 def saveLibrary(self, + 16 library: str, + 17 saveFileName: str, + 18 dev: str = None, + 19 vol: str = None, + 20 toLibrary: str = None, + 21 description: str = None, + 22 localPath: str = None, + 23 remPath: str = None, + 24 getZip: bool = False, + 25 port: int = None, + 26 remSavf=True, + 27 version: str = None, + 28 max_records: Union[int, str, None] = None, + 29 asp: Union[int, str, None] = None, + 30 waitFile: Union[int, str, None] = None, + 31 share: str = None, + 32 authority: str = None + 33 ) -> bool: + 34 """ + 35 Saves a library to a specified save file, providing options for further customization such + 36 as setting the target release, saving as a zip file, specifying the device, volume, and more. + 37 + 38 :param library: The name of the library to be saved. Must be a valid library name or one of + 39 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. + 40 :type library: str + 41 :param saveFileName: The name of the save file where the library will be saved. + 42 :type saveFileName: str + 43 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. + 44 :type dev: str, optional + 45 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. + 46 :type vol: str, optional + 47 :param toLibrary: Target library where the save file will be temporarily stored. Defaults + 48 to the value of `library` if not specified. + 49 :type toLibrary: str, optional + 50 :param description: An optional description for the save file to be created. + 51 :type description: str, optional + 52 :param localPath: The local path where the save file will be downloaded if `getZip` is set + 53 to True. Must be an absolute path. + 54 :type localPath: str, optional + 55 :param remPath: The remote directory path on the target system to temporarily store the + 56 save file if `getZip` is set to True. Must be an absolute path. + 57 :type remPath: str, optional + 58 :param getZip: A flag that determines whether the save file should be archived into a zip + 59 file and downloaded locally. + 60 :type getZip: bool + 61 :param port: Specifies the port to be used for transferring the save file when `getZip` is + 62 enabled. + 63 :type port: int, optional + 64 :param remSavf: A flag indicating whether the save file should be removed from the remote + 65 target system after a successful save. + 66 :type remSavf: bool + 67 :param version: The target release version for the save operation. Valid values include + 68 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. + 69 :type version: str, optional + 70 :param max_records: Optional parameter for specifying the maximum number of records in + 71 the save file. + 72 :type max_records: Union[int, str, None], optional + 73 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. + 74 :type asp: Union[int, str, None], optional + 75 :param waitFile: The amount of time to wait for file access locks to be released. + 76 :type waitFile: Union[int, str, None], optional + 77 :param share: Specifies the share handling for threads or users accessing the save file. + 78 :type share: str, optional + 79 :param authority: Authority option to set for the save file being saved. + 80 :type authority: str, optional + 81 :return: A boolean indicating whether the library was successfully saved. Returns True on + 82 success or False on failure. + 83 :rtype: bool + 84 """ + 85 # Target Release List + 86 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", + 87 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", + 88 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", + 89 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", + 90 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] + 91 + 92 # check if something missing from the Arguments + 93 # check if Library is empty or not + 94 if not library: + 95 raise ValueError("A library name is required.") + 96 # check if saveFileName is empty or not + 97 if not saveFileName: + 98 raise ValueError("A save file name is required.") + 99 # check if toLibrary is empty or not +100 if not toLibrary: +101 toLibrary = library +102 # check if user want the SaveFile as ZIP File +103 if getZip: +104 if not remPath: +105 raise ValueError("A remote path is required. Use 'remPath' instead.") +106 elif remPath[-1] == '/': +107 remPath = remPath[:-1] +108 if not localPath: +109 raise ValueError("A local path is required. Use 'localPath' instead.") +110 elif localPath[-1] == '/': +111 localPath = localPath[:-1] +112 # check wich Version of SaveFile is wanted +113 if not version in list(trgList): +114 version = "*CURRENT" +115 else: +116 version = version.upper() +117 command_str: str = f'SAVLIB' +118 +119 # check if Library is valid or not +120 validated_library = self.__validate_max_value(value=library, param_name='library', +121 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', +122 library]) +123 if validated_library: +124 command_str += f' LIB({validated_library})' +125 else: +126 library_str = str(library) +127 raise ValueError( +128 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") +129 # check Dev - Device +130 if not dev in ['*SAVF', '*MEDDFN']: +131 command_str += f' DEV(*SAVF)' +132 else: +133 command_str += f' DEV({dev.upper()})' +134 if vol is not None and vol == '*MOUNTED': +135 command_str += f' VOL({vol})' +136 # starting with mem main Sourcecode of saveLLibrary +137 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, +138 share=share, authority=authority): +139 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +140 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +141 #print(command_str) +142 try: +143 with self.conn.cursor() as cursor: +144 # execute the Command for creating a Savefile. +145 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +146 if getZip: +147 try: +148 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') +149 +150 destination_local_path = join(localPath, saveFileName.upper() + '.savf') +151 command_str = ( +152 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " +153 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" +154 ) +155 +156 # Execute the command on the remote system +157 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) +158 +159 if self.__getSavFile(localFilePath=destination_local_path, +160 remotePath=remote_temp_savf_path, port=port): +161 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" +162 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) +163 else: +164 raise ValueError("Something went wrong. With downloading the Save File.") +165 if remSavf: +166 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): +167 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") +168 +169 except Exception as e: +170 self.__handle_error(error=e, pgm="saveLibrary - Transfer") +171 +172 except Exception as e: +173 self.__handle_error(error=e, pgm="saveLibrary") +174 self.conn.rollback() +175 return False +176 else: +177 self.conn.commit() +178 if getZip: +179 print(f"File successfully downloaded to: {destination_local_path}") +180 return True +181 +182 print(f"Successfully saved in the Library '{library}' successfully.") +183 return True +184 +185 return False +186 +187 # ------------------------------------------------------ +188 # sub Function: create the Savefile on the AS400 +189 # ------------------------------------------------------ +190 def __crtsavf(self, +191 saveFileName: str, +192 library: str, +193 description: str = None, +194 max_records: Union[int, str, None] = None, +195 asp: Union[int, str, None] = None, +196 waitFile: Union[int, str, None] = None, +197 share: str = None, +198 authority: str = None +199 ) -> bool: +200 """ +201 Sub-function to create a save file on the IBM i server. +202 +203 This function executes the `CRTSAVF` (Create Save File) CL command +204 to create a new save file in the specified library. This is a +205 prerequisite for saving a library's contents. +206 +207 Args: +208 saveFileName (str): The name of the save file to be created. +209 This will be the AS/400 object name. +210 library (str): The name of the library where the save file will be created. +211 description (str, optional): A text description for the save file. Defaults to None. +212 +213 Returns: +214 bool: True if the save file was created successfully, False otherwise. +215 """ +216 # check is a parameter empty or not +217 +218 if not saveFileName: +219 raise ValueError("A file name is required.") +220 if not library: +221 raise ValueError("A library name is required.") +222 if not description: +223 description = 'A SaveFile from iLibrary' +224 +225 command_str: str = f"CRTSAVF FILE({library.upper().strip()}/{saveFileName.upper().strip()}) TEXT('{description.strip()}')" +226 +227 # check max_records for MAXRCDS parameter +228 if self.__validate_max_value(value=max_records, param_name='max_records', str_format=['*NOMAX'], +229 max_limit=4293525600) and not None: +230 command_str += f" MAXRCDS({max_records})" +231 # check asp for ASP 2147483647 +232 if self.__validate_max_value(value=asp, param_name='asp', str_format=['*LIBASP'], max_limit=32) and not None: +233 command_str += f" ASP({asp})" +234 if self.__validate_max_value(value=waitFile, param_name='waitFile', str_format=['*IMMED', '*CLS'], +235 max_limit=2147483647) and not None: +236 command_str += f" WAITFILE({waitFile})" +237 if self.__validate_max_value(value=share, param_name='share', str_format=['*YES', '*NO']) and not None: +238 command_str += f" SHARE({share})" +239 +240 if authority is not None: +241 upper_authority = authority.upper() +242 +243 # 1. Check for custom authority (not in list AND up to 10 chars) +244 if upper_authority not in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE'] and len( +245 upper_authority) <= 10: +246 # **CORRECTION 1: Use upper_authority here, not the undefined 'auth'** +247 command_str += f" AUT({upper_authority})" +248 # The 'pass' statements are redundant and can be removed +249 +250 # 2. Add an 'elif' to handle the case where it IS one of the standard values +251 elif upper_authority in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE']: +252 command_str += f" AUT({upper_authority})" +253 +254 +255 try: +256 with self.conn.cursor() as cursor: +257 # execute the Command for creating a Savefile. +258 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +259 +260 except Exception as e: +261 self.__handle_error(error=e, pgm="__crtsavf") +262 # remove a SAVF if its exists and we got an error +263 if e.args[0] == 'HY000': +264 sql = """ +265 SELECT 1 +266 FROM QSYS2.SAVE_FILE_INFO +267 WHERE SAVE_FILE_LIBRARY = ? \ +268 AND SAVE_FILE = ? +269 FETCH FIRST 1 ROW ONLY \ +270 """ +271 cursor = self.conn.cursor() +272 cursor.execute(sql, library, saveFileName) +273 result = cursor.fetchone() +274 if result is not None: +275 self.removeFile(library=library, saveFileName=saveFileName) +276 self.conn.rollback() +277 raise ValueError(e) +278 else: +279 self.conn.commit() +280 return True +281 +282 # -------------------------------------------------------------------------- +283 # __validate_max_value - Helper Function for checking parameter +284 # -------------------------------------------------------------------------- +285 def __validate_max_value(self, +286 value: Union[int, str, None], +287 param_name: str, +288 str_format: list[str], +289 min_limit: int = 1, +290 max_limit: int = None +291 ) -> Union[int, str, bool]: # Includes bool as requested +292 """ +293 Validates an input value for 'MAX' type parameters against a custom range. +294 Handles special strings defined in str_format and numeric values. +295 +296 Returns: The validated integer, the standardized special string, or False on failure (if no exception is raised). +297 Raises: ValueError for invalid string format or out-of-range number. +298 """ +299 +300 # Helper for clear error messages +301 str_options = ", ".join([f"'{s}'" for s in str_format]) +302 +303 # 1. Handle special string +304 if isinstance(value, str): +305 upper_value = value.upper() +306 +307 for special_value in str_format: +308 normalized_special_value = special_value.upper() +309 +310 if upper_value == special_value.upper() or upper_value == normalized_special_value: +311 # Found a match! Return the official, fully formatted string. +312 return special_value +313 +314 # 2. Attempt Numeric Conversion (handles int and string-of-int) +315 if value is not None: +316 try: +317 numeric_value = int(value) +318 except ValueError: +319 # Value is an invalid string (e.g., 'hello') +320 raise ValueError( +321 f"Invalid value for {param_name}. Must be '{str_format}' or a number " +322 f"between {min_limit} and {max_limit:,}." +323 ) +324 else: +325 # If the value is None +326 return False +327 +328 # 3. Check Numeric Range +329 if min_limit <= numeric_value <= max_limit: +330 return numeric_value +331 else: +332 # Number is out of range +333 raise ValueError( +334 f"Invalid numeric value for {param_name}. Must be between {min_limit} and {max_limit:,}. " +335 f"Received: {numeric_value}" +336 ) +337 +338 # ------------------------------------------------------ +339 # getZipFile - getting the Zipfile from the SaveFile +340 # ------------------------------------------------------ +341 def __getSavFile(self, +342 localFilePath: str, +343 remotePath: str, +344 port: int = None +345 ) -> bool: +346 """ +347 Downloads a file from the remote IBM i via SFTP. +348 +349 This method uses Paramiko to establish a secure shell (SSH) connection and +350 then an SFTP session to transfer a file from a specified remote location +351 on the IBM i's IFS to a local path. +352 +353 Args: +354 localFilePath (str): The full path to the file on the remote IBM i's IFS. +355 remotePath (str): The full path on the local machine where the file +356 will be saved. For example, '/Users/user/Documents/somefile.savf'. +357 port (int, optional): The port to connect to the IBMi server. Defaults to None. +358 +359 Returns: +360 bool: True if the file was downloaded successfully, False otherwise. +361 +362 Raises: +363 ValueError: If either the remote_file_path or local_save_path is not provided. +364 """ +365 if not localFilePath: +366 print("Error: A local file path is required.") +367 return False +368 if not remotePath: +369 print("Error: A remote path is required.") +370 return False +371 if not port: +372 port = 2222 +373 +374 remotePath = PureWindowsPath(remotePath).as_posix() +375 ssh_client = paramiko.SSHClient() +376 +377 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +378 +379 try: +380 with ssh_client: +381 ssh_client.connect( +382 hostname=self.db_host, +383 username=self.db_user, +384 password=self.db_password, +385 port=port +386 ) +387 with ssh_client.open_sftp() as ftp_client: +388 ftp_client.get(remotePath, localFilePath) +389 return True +390 +391 except paramiko.ssh_exception.AuthenticationException as e: +392 print(f"Authentication failed. Check your username and password: {e}") +393 return False +394 except paramiko.ssh_exception.SSHException as e: +395 print(f"SSH error occurred: {e}") +396 return False +397 except FileNotFoundError as e: +398 print(f"File not found on the remote host: {e}") +399 return False +400 +401 finally: +402 pass +403 +404 def removeFile(self, library: str, saveFileName: str) -> bool: +405 """ +406 Removes a save file from the specified library. +407 +408 This function executes the system command to delete a save file from an IBM i +409 system. It connects to the database through a cursor, and attempts to perform +410 the operation. If an error is encountered during execution, the function +411 rolls back the transaction and logs the error. On success, the transaction +412 is committed. +413 +414 :param library: The name of the library containing the save file to be removed. +415 :type library: str +416 :param saveFileName: The name of the save file to be removed. +417 :type saveFileName: str +418 :return: True if the save file is removed successfully, otherwise False. +419 :rtype: bool +420 """ +421 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" +422 try: +423 with self.conn.cursor() as cursor: +424 # execute the Command for deleting a Savefile. +425 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +426 +427 except Exception as e: +428 self.__handle_error(error=e, pgm="removeFile") +429 self.conn.rollback() +430 return False +431 else: +432 self.conn.commit() +433 return True +434 +435 def __handle_error(self, error, pgm: str): +436 """ +437 Handles errors encountered during the execution of a command. +438 +439 This method processes an error raised during the execution of a command in a +440 specific function and extracts detailed error information including SQLSTATE +441 and the error message. The formatted details are printed to the console for +442 debugging purposes. +443 +444 :param error: The error object encountered during command execution. +445 :type error: Exception +446 :param pgm: The name of the function where the error occurred. +447 :type pgm: str +448 :return: None +449 """ +450 print("-------------------------------------------------------------") +451 print(f"An error occurred while executing command in function {pgm}:") +452 sqlstate = error.args[0] +453 error_message = error.args[1] +454 +455 print(f"SQLSTATE: {sqlstate}") +456 print(f"Message: {error_message}") +
15 def saveLibrary(self, + 16 library: str, + 17 saveFileName: str, + 18 dev: str = None, + 19 vol: str = None, + 20 toLibrary: str = None, + 21 description: str = None, + 22 localPath: str = None, + 23 remPath: str = None, + 24 getZip: bool = False, + 25 port: int = None, + 26 remSavf=True, + 27 version: str = None, + 28 max_records: Union[int, str, None] = None, + 29 asp: Union[int, str, None] = None, + 30 waitFile: Union[int, str, None] = None, + 31 share: str = None, + 32 authority: str = None + 33 ) -> bool: + 34 """ + 35 Saves a library to a specified save file, providing options for further customization such + 36 as setting the target release, saving as a zip file, specifying the device, volume, and more. + 37 + 38 :param library: The name of the library to be saved. Must be a valid library name or one of + 39 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. + 40 :type library: str + 41 :param saveFileName: The name of the save file where the library will be saved. + 42 :type saveFileName: str + 43 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. + 44 :type dev: str, optional + 45 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. + 46 :type vol: str, optional + 47 :param toLibrary: Target library where the save file will be temporarily stored. Defaults + 48 to the value of `library` if not specified. + 49 :type toLibrary: str, optional + 50 :param description: An optional description for the save file to be created. + 51 :type description: str, optional + 52 :param localPath: The local path where the save file will be downloaded if `getZip` is set + 53 to True. Must be an absolute path. + 54 :type localPath: str, optional + 55 :param remPath: The remote directory path on the target system to temporarily store the + 56 save file if `getZip` is set to True. Must be an absolute path. + 57 :type remPath: str, optional + 58 :param getZip: A flag that determines whether the save file should be archived into a zip + 59 file and downloaded locally. + 60 :type getZip: bool + 61 :param port: Specifies the port to be used for transferring the save file when `getZip` is + 62 enabled. + 63 :type port: int, optional + 64 :param remSavf: A flag indicating whether the save file should be removed from the remote + 65 target system after a successful save. + 66 :type remSavf: bool + 67 :param version: The target release version for the save operation. Valid values include + 68 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. + 69 :type version: str, optional + 70 :param max_records: Optional parameter for specifying the maximum number of records in + 71 the save file. + 72 :type max_records: Union[int, str, None], optional + 73 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. + 74 :type asp: Union[int, str, None], optional + 75 :param waitFile: The amount of time to wait for file access locks to be released. + 76 :type waitFile: Union[int, str, None], optional + 77 :param share: Specifies the share handling for threads or users accessing the save file. + 78 :type share: str, optional + 79 :param authority: Authority option to set for the save file being saved. + 80 :type authority: str, optional + 81 :return: A boolean indicating whether the library was successfully saved. Returns True on + 82 success or False on failure. + 83 :rtype: bool + 84 """ + 85 # Target Release List + 86 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", + 87 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", + 88 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", + 89 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", + 90 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] + 91 + 92 # check if something missing from the Arguments + 93 # check if Library is empty or not + 94 if not library: + 95 raise ValueError("A library name is required.") + 96 # check if saveFileName is empty or not + 97 if not saveFileName: + 98 raise ValueError("A save file name is required.") + 99 # check if toLibrary is empty or not +100 if not toLibrary: +101 toLibrary = library +102 # check if user want the SaveFile as ZIP File +103 if getZip: +104 if not remPath: +105 raise ValueError("A remote path is required. Use 'remPath' instead.") +106 elif remPath[-1] == '/': +107 remPath = remPath[:-1] +108 if not localPath: +109 raise ValueError("A local path is required. Use 'localPath' instead.") +110 elif localPath[-1] == '/': +111 localPath = localPath[:-1] +112 # check wich Version of SaveFile is wanted +113 if not version in list(trgList): +114 version = "*CURRENT" +115 else: +116 version = version.upper() +117 command_str: str = f'SAVLIB' +118 +119 # check if Library is valid or not +120 validated_library = self.__validate_max_value(value=library, param_name='library', +121 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', +122 library]) +123 if validated_library: +124 command_str += f' LIB({validated_library})' +125 else: +126 library_str = str(library) +127 raise ValueError( +128 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") +129 # check Dev - Device +130 if not dev in ['*SAVF', '*MEDDFN']: +131 command_str += f' DEV(*SAVF)' +132 else: +133 command_str += f' DEV({dev.upper()})' +134 if vol is not None and vol == '*MOUNTED': +135 command_str += f' VOL({vol})' +136 # starting with mem main Sourcecode of saveLLibrary +137 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, +138 share=share, authority=authority): +139 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +140 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" +141 #print(command_str) +142 try: +143 with self.conn.cursor() as cursor: +144 # execute the Command for creating a Savefile. +145 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +146 if getZip: +147 try: +148 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') +149 +150 destination_local_path = join(localPath, saveFileName.upper() + '.savf') +151 command_str = ( +152 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " +153 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" +154 ) +155 +156 # Execute the command on the remote system +157 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) +158 +159 if self.__getSavFile(localFilePath=destination_local_path, +160 remotePath=remote_temp_savf_path, port=port): +161 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" +162 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) +163 else: +164 raise ValueError("Something went wrong. With downloading the Save File.") +165 if remSavf: +166 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): +167 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") +168 +169 except Exception as e: +170 self.__handle_error(error=e, pgm="saveLibrary - Transfer") +171 +172 except Exception as e: +173 self.__handle_error(error=e, pgm="saveLibrary") +174 self.conn.rollback() +175 return False +176 else: +177 self.conn.commit() +178 if getZip: +179 print(f"File successfully downloaded to: {destination_local_path}") +180 return True +181 +182 print(f"Successfully saved in the Library '{library}' successfully.") +183 return True +184 +185 return False +
Saves a library to a specified save file, providing options for further customization such +as setting the target release, saving as a zip file, specifying the device, volume, and more.
+ +library if not specified.getZip is set
+to True. Must be an absolute path.getZip is set to True. Must be an absolute path.getZip is
+enabled.++A boolean indicating whether the library was successfully saved. Returns True on + success or False on failure.
+
404 def removeFile(self, library: str, saveFileName: str) -> bool: +405 """ +406 Removes a save file from the specified library. +407 +408 This function executes the system command to delete a save file from an IBM i +409 system. It connects to the database through a cursor, and attempts to perform +410 the operation. If an error is encountered during execution, the function +411 rolls back the transaction and logs the error. On success, the transaction +412 is committed. +413 +414 :param library: The name of the library containing the save file to be removed. +415 :type library: str +416 :param saveFileName: The name of the save file to be removed. +417 :type saveFileName: str +418 :return: True if the save file is removed successfully, otherwise False. +419 :rtype: bool +420 """ +421 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" +422 try: +423 with self.conn.cursor() as cursor: +424 # execute the Command for deleting a Savefile. +425 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) +426 +427 except Exception as e: +428 self.__handle_error(error=e, pgm="removeFile") +429 self.conn.rollback() +430 return False +431 else: +432 self.conn.commit() +433 return True +
Removes a save file from the specified library.
+ +This function executes the system command to delete a save file from an IBM i +system. It connects to the database through a cursor, and attempts to perform +the operation. If an error is encountered during execution, the function +rolls back the transaction and logs the error. On success, the transaction +is committed.
+ +++True if the save file is removed successfully, otherwise False.
+
1from os.path import join + 2import paramiko + 3import pyodbc + 4import json + 5from datetime import datetime, date + 6from decimal import Decimal + 7 + 8class sendMSG(): + 9 """ +10 Handles message-related operations by providing functionality to send messages +11 to specific users within the system. The class interacts with system APIs to +12 execute the required operations and ensures the input parameters are validated +13 before proceeding with the message sending process. +14 +15 Attributes supported by this class are not specified because the class relies +16 on method-level operations. +17 """ +18 def send_message_to_user( +19 self, +20 username: str, +21 message: str, +22 # tomsgq: str = None, +23 # msgtype: str = None, +24 # rpymsgq: str = None, +25 ccsid: int = None +26 ): +27 """ +28 Sends a message to a specified user on the system. This method interacts with the system +29 to send a message by executing an SQL query. It validates the required +30 inputs and raises an exception if they are missing. Optional parameters for +31 further message configuration can also be provided. +32 +33 :param username: The username of the recipient to whom the message will be sent. +34 :type username: str +35 :param message: The actual text message to be sent to the user. +36 :type message: str +37 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. +38 :type ccsid: int, optional +39 +40 :return: None if the message is sent successfully. +41 :rtype: None +42 +43 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. +44 +45 :raises Exception: Any other exceptions that occur during the execution of the +46 SQL query are raised, indicating issues during the process of sending the +47 message. +48 """ +49 if not username: +50 raise ValueError("Username are required.") +51 if not message: +52 raise ValueError("Message are required.") +53 +54 username = username.upper() +55 message = message.upper() +56 +57 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" +58 if ccsid: +59 sql_query += f" CCSID({ccsid})" +60 try: +61 with self.conn.cursor() as cursor: +62 cursor.execute(sql_query) +63 row_dict:dict = {"success": f'Message sent to {username}'} +64 return json.dumps(row_dict, indent=4) +65 except Exception as e: +66 row_dict: dict = {"error" : f'Error: {e}'} +67 return json.dumps(row_dict, indent=4) +
9class sendMSG(): +10 """ +11 Handles message-related operations by providing functionality to send messages +12 to specific users within the system. The class interacts with system APIs to +13 execute the required operations and ensures the input parameters are validated +14 before proceeding with the message sending process. +15 +16 Attributes supported by this class are not specified because the class relies +17 on method-level operations. +18 """ +19 def send_message_to_user( +20 self, +21 username: str, +22 message: str, +23 # tomsgq: str = None, +24 # msgtype: str = None, +25 # rpymsgq: str = None, +26 ccsid: int = None +27 ): +28 """ +29 Sends a message to a specified user on the system. This method interacts with the system +30 to send a message by executing an SQL query. It validates the required +31 inputs and raises an exception if they are missing. Optional parameters for +32 further message configuration can also be provided. +33 +34 :param username: The username of the recipient to whom the message will be sent. +35 :type username: str +36 :param message: The actual text message to be sent to the user. +37 :type message: str +38 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. +39 :type ccsid: int, optional +40 +41 :return: None if the message is sent successfully. +42 :rtype: None +43 +44 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. +45 +46 :raises Exception: Any other exceptions that occur during the execution of the +47 SQL query are raised, indicating issues during the process of sending the +48 message. +49 """ +50 if not username: +51 raise ValueError("Username are required.") +52 if not message: +53 raise ValueError("Message are required.") +54 +55 username = username.upper() +56 message = message.upper() +57 +58 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" +59 if ccsid: +60 sql_query += f" CCSID({ccsid})" +61 try: +62 with self.conn.cursor() as cursor: +63 cursor.execute(sql_query) +64 row_dict:dict = {"success": f'Message sent to {username}'} +65 return json.dumps(row_dict, indent=4) +66 except Exception as e: +67 row_dict: dict = {"error" : f'Error: {e}'} +68 return json.dumps(row_dict, indent=4) +
Handles message-related operations by providing functionality to send messages +to specific users within the system. The class interacts with system APIs to +execute the required operations and ensures the input parameters are validated +before proceeding with the message sending process.
+ +Attributes supported by this class are not specified because the class relies +on method-level operations.
+19 def send_message_to_user( +20 self, +21 username: str, +22 message: str, +23 # tomsgq: str = None, +24 # msgtype: str = None, +25 # rpymsgq: str = None, +26 ccsid: int = None +27 ): +28 """ +29 Sends a message to a specified user on the system. This method interacts with the system +30 to send a message by executing an SQL query. It validates the required +31 inputs and raises an exception if they are missing. Optional parameters for +32 further message configuration can also be provided. +33 +34 :param username: The username of the recipient to whom the message will be sent. +35 :type username: str +36 :param message: The actual text message to be sent to the user. +37 :type message: str +38 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. +39 :type ccsid: int, optional +40 +41 :return: None if the message is sent successfully. +42 :rtype: None +43 +44 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. +45 +46 :raises Exception: Any other exceptions that occur during the execution of the +47 SQL query are raised, indicating issues during the process of sending the +48 message. +49 """ +50 if not username: +51 raise ValueError("Username are required.") +52 if not message: +53 raise ValueError("Message are required.") +54 +55 username = username.upper() +56 message = message.upper() +57 +58 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" +59 if ccsid: +60 sql_query += f" CCSID({ccsid})" +61 try: +62 with self.conn.cursor() as cursor: +63 cursor.execute(sql_query) +64 row_dict:dict = {"success": f'Message sent to {username}'} +65 return json.dumps(row_dict, indent=4) +66 except Exception as e: +67 row_dict: dict = {"error" : f'Error: {e}'} +68 return json.dumps(row_dict, indent=4) +
Sends a message to a specified user on the system. This method interacts with the system +to send a message by executing an SQL query. It validates the required +inputs and raises an exception if they are missing. Optional parameters for +further message configuration can also be provided.
+ +++ +None if the message is sent successfully.
+
ValueError: If any required parameter, such as username or message, is missing.
Exception: Any other exceptions that occur during the execution of the +SQL query are raised, indicating issues during the process of sending the +message.
A class to manage libraries and files on an IBM i system.
\n\nIt provides methods to connect to the system via pyodbc for SQL and\nparamiko for SFTP transfers.
\n", "bases": "iLibrary.src.getInfoForLibrary.getInfoForLibrary, iLibrary.src.saveLibrary.saveLibrary"}, {"fullname": "iLibrary.src.Library.Library.__init__", "modulename": "iLibrary.src.Library", "qualname": "Library.__init__", "kind": "function", "doc": "Initializes the class attributes for a database connection.\nThe actual connection is established in the __enter__ method.
\n\nArgs:\n db_user (str): The user ID for the database connection.\n db_password (str): The password for the database user.\n db_host (str): The system/host name for the database connection.\n db_driver (str): The ODBC driver to be used.
\n", "signature": "(db_user: str, db_password: str, db_host: str, db_driver: str)"}, {"fullname": "iLibrary.src.Library.Library.db_user", "modulename": "iLibrary.src.Library", "qualname": "Library.db_user", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_host", "modulename": "iLibrary.src.Library", "qualname": "Library.db_host", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_driver", "modulename": "iLibrary.src.Library", "qualname": "Library.db_driver", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_password", "modulename": "iLibrary.src.Library", "qualname": "Library.db_password", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.iclose", "modulename": "iLibrary.src.Library", "qualname": "Library.iclose", "kind": "function", "doc": "A helper method to close the connection, also useful for manual closure.
\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.User", "modulename": "iLibrary.src.User", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.User.User", "modulename": "iLibrary.src.User", "qualname": "User", "kind": "class", "doc": "A class to manage User on IBMi System
\n\nIt provides methods to connect to the system via pyodbc for SQL and\nparamiko for SFTP transfers.
\n", "bases": "iLibrary.src.getUserInfoForUser.getUserInfoForUser, iLibrary.src.sendMSG.sendMSG"}, {"fullname": "iLibrary.src.User.User.__init__", "modulename": "iLibrary.src.User", "qualname": "User.__init__", "kind": "function", "doc": "Initializes the class attributes for a database connection.\nThe actual connection is established in the __enter__ method.
\n\nArgs:\n db_user (str): The user ID for the database connection.\n db_password (str): The password for the database user.\n db_host (str): The system/host name for the database connection.\n db_driver (str): The ODBC driver to be used.
\n", "signature": "(db_user: str, db_password: str, db_host: str, db_driver: str)"}, {"fullname": "iLibrary.src.User.User.db_user", "modulename": "iLibrary.src.User", "qualname": "User.db_user", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_host", "modulename": "iLibrary.src.User", "qualname": "User.db_host", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_driver", "modulename": "iLibrary.src.User", "qualname": "User.db_driver", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_password", "modulename": "iLibrary.src.User", "qualname": "User.db_password", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.iclose", "modulename": "iLibrary.src.User", "qualname": "User.iclose", "kind": "function", "doc": "A helper method to close the connection, also useful for manual closure.
\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary", "modulename": "iLibrary.src.getInfoForLibrary", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary", "kind": "class", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.__init__", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.__init__", "kind": "function", "doc": "\n", "signature": "(connection)"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.conn", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.conn", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getLibraryInfo", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getLibraryInfo", "kind": "function", "doc": "\n", "signature": "(self, library: str, wantJson=True):", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getFileInfo", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getFileInfo", "kind": "function", "doc": "\n", "signature": "(self, library: str, qFiles: bool = False) -> str:", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getAllLibraries", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getAllLibraries", "kind": "function", "doc": "\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.getUserInfoForUser", "modulename": "iLibrary.src.getUserInfoForUser", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser", "kind": "class", "doc": "Handles user information retrieval and messaging functionalities.
\n\nThis class provides methods to interact with the database for retrieving user information\nand to send messages to specified users. It supports data retrieval in different formats\n(e.g., JSON or tuple), and it enables system messaging with configurable options.
\n\n:ivar conn: Database connection object used for executing queries.
\n"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser.getAllUsers", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser.getAllUsers", "kind": "function", "doc": "Retrieves all user information from the database. Optionally returns the data in\nJSON format depending on the provided parameter.
\n\nRetrieves a list of users stored in the database and can output the data either as\na list of tuples or in JSON format. The query fetches all fields available in the\nuser information database table and handles cases where no data is found.
\n\n\n\n", "signature": "(self, wantJson: bool = False):", "funcdef": "def"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser.getSingleUserInformation", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser.getSingleUserInformation", "kind": "function", "doc": "The data fetched from the database. When
\nwantJsonis True, returns a\n JSON object as a string. Otherwise, returns a list of tuples.
Retrieves information about a specific user from the database based on their username. The function supports\nreturning data either as a JSON-formatted string or as a tuple with corresponding database fields.
\n\n\n\n\nA tuple containing database fields if
\nwantJsonis False, or a JSON-formatted string ifwantJsonis True.\n If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the\n value ofwantJson. Returns None if an exception occurs.
username input is empty or None.Saves a library to a specified save file, providing options for further customization such\nas setting the target release, saving as a zip file, specifying the device, volume, and more.
\n\nlibrary if not specified.getZip is set\nto True. Must be an absolute path.getZip is set to True. Must be an absolute path.getZip is\nenabled.\n\n", "signature": "(\tself,\tlibrary: str,\tsaveFileName: str,\tdev: str = None,\tvol: str = None,\ttoLibrary: str = None,\tdescription: str = None,\tlocalPath: str = None,\tremPath: str = None,\tgetZip: bool = False,\tport: int = None,\tremSavf=True,\tversion: str = None,\tmax_records: int | str | None = None,\tasp: int | str | None = None,\twaitFile: int | str | None = None,\tshare: str = None,\tauthority: str = None) -> bool:", "funcdef": "def"}, {"fullname": "iLibrary.src.saveLibrary.saveLibrary.removeFile", "modulename": "iLibrary.src.saveLibrary", "qualname": "saveLibrary.removeFile", "kind": "function", "doc": "A boolean indicating whether the library was successfully saved. Returns True on\n success or False on failure.
\n
Removes a save file from the specified library.
\n\nThis function executes the system command to delete a save file from an IBM i\nsystem. It connects to the database through a cursor, and attempts to perform\nthe operation. If an error is encountered during execution, the function\nrolls back the transaction and logs the error. On success, the transaction\nis committed.
\n\n\n\n", "signature": "(self, library: str, saveFileName: str) -> bool:", "funcdef": "def"}, {"fullname": "iLibrary.src.sendMSG", "modulename": "iLibrary.src.sendMSG", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.sendMSG.sendMSG", "modulename": "iLibrary.src.sendMSG", "qualname": "sendMSG", "kind": "class", "doc": "True if the save file is removed successfully, otherwise False.
\n
Handles message-related operations by providing functionality to send messages\nto specific users within the system. The class interacts with system APIs to\nexecute the required operations and ensures the input parameters are validated\nbefore proceeding with the message sending process.
\n\nAttributes supported by this class are not specified because the class relies\non method-level operations.
\n"}, {"fullname": "iLibrary.src.sendMSG.sendMSG.send_message_to_user", "modulename": "iLibrary.src.sendMSG", "qualname": "sendMSG.send_message_to_user", "kind": "function", "doc": "Sends a message to a specified user on the system. This method interacts with the system\nto send a message by executing an SQL query. It validates the required\ninputs and raises an exception if they are missing. Optional parameters for\nfurther message configuration can also be provided.
\n\n\n\n\nNone if the message is sent successfully.
\n
ValueError: If any required parameter, such as username or message, is missing.
Exception: Any other exceptions that occur during the execution of the\nSQL query are raised, indicating issues during the process of sending the\nmessage.
1
-1from os.path import join - 2import paramiko - 3import pyodbc - 4import json - 5from datetime import datetime, date - 6from decimal import Decimal - 7from .getInfoForLibrary import * - 8from .saveLibrary import * - 9 -10 -11 -12class Library(getInfoForLibrary, saveLibrary): -13 """ -14 A class to manage libraries and files on an IBM i system. -15 -16 It provides methods to connect to the system via pyodbc for SQL and -17 paramiko for SFTP transfers. -18 """ -19 -20 # ------------------------------------------------------ -21 # __init__ - initzialise the class -22 # ------------------------------------------------------ -23 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -24 """ -25 Initializes the class attributes for a database connection. -26 The actual connection is established in the __enter__ method. -27 -28 Args: -29 db_user (str): The user ID for the database connection. -30 db_password (str): The password for the database user. -31 db_host (str): The system/host name for the database connection. -32 db_driver (str): The ODBC driver to be used. -33 """ -34 self.db_user = db_user -35 self.db_host = db_host -36 self.db_driver = db_driver -37 self.db_password = db_password -38 -39 # ------------------------------------------------------ -40 # __enter__ - enter to the class -41 # ------------------------------------------------------ -42 def __enter__(self) -> 'Library': -43 """ -44 Establishes the database connection when entering a 'with' block. -45 """ -46 try: -47 conn_str = ( -48 f"DRIVER={self.db_driver};" -49 f"SYSTEM={self.db_host};" -50 f"UID={self.db_user};" -51 f"PWD={self.db_password};" -52 ) -53 self.conn = pyodbc.connect(conn_str, autocommit=True) -54 return self -55 except pyodbc.Error as ex: -56 sqlstate = ex.args[0] -57 print(f"Database connection failed with error: {sqlstate}") -58 raise -59 -60 # ------------------------------------------------------ -61 # __exit__ - leave the class -62 # ------------------------------------------------------ -63 def __exit__(self, exc_type, exc_val, exc_tb): -64 """ -65 Closes the database connection when exiting a 'with' block. -66 This method is called automatically, even if an error occurred. -67 """ -68 self.iclose() -69 -70 -71 # ------------------------------------------------------ -72 # iClose - close connection -73 # ------------------------------------------------------ -74 def iclose(self): -75 """ -76 A helper method to close the connection, also useful for manual closure. -77 """ -78 if self.conn and not self.conn.closed: -79 self.conn.close() -80 pass -
13class Library(getInfoForLibrary, saveLibrary): -14 """ -15 A class to manage libraries and files on an IBM i system. -16 -17 It provides methods to connect to the system via pyodbc for SQL and -18 paramiko for SFTP transfers. -19 """ -20 -21 # ------------------------------------------------------ -22 # __init__ - initzialise the class -23 # ------------------------------------------------------ -24 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -25 """ -26 Initializes the class attributes for a database connection. -27 The actual connection is established in the __enter__ method. -28 -29 Args: -30 db_user (str): The user ID for the database connection. -31 db_password (str): The password for the database user. -32 db_host (str): The system/host name for the database connection. -33 db_driver (str): The ODBC driver to be used. -34 """ -35 self.db_user = db_user -36 self.db_host = db_host -37 self.db_driver = db_driver -38 self.db_password = db_password -39 -40 # ------------------------------------------------------ -41 # __enter__ - enter to the class -42 # ------------------------------------------------------ -43 def __enter__(self) -> 'Library': -44 """ -45 Establishes the database connection when entering a 'with' block. -46 """ -47 try: -48 conn_str = ( -49 f"DRIVER={self.db_driver};" -50 f"SYSTEM={self.db_host};" -51 f"UID={self.db_user};" -52 f"PWD={self.db_password};" -53 ) -54 self.conn = pyodbc.connect(conn_str, autocommit=True) -55 return self -56 except pyodbc.Error as ex: -57 sqlstate = ex.args[0] -58 print(f"Database connection failed with error: {sqlstate}") -59 raise -60 -61 # ------------------------------------------------------ -62 # __exit__ - leave the class -63 # ------------------------------------------------------ -64 def __exit__(self, exc_type, exc_val, exc_tb): -65 """ -66 Closes the database connection when exiting a 'with' block. -67 This method is called automatically, even if an error occurred. -68 """ -69 self.iclose() -70 -71 -72 # ------------------------------------------------------ -73 # iClose - close connection -74 # ------------------------------------------------------ -75 def iclose(self): -76 """ -77 A helper method to close the connection, also useful for manual closure. -78 """ -79 if self.conn and not self.conn.closed: -80 self.conn.close() -81 pass -
A class to manage libraries and files on an IBM i system.
- -It provides methods to connect to the system via pyodbc for SQL and -paramiko for SFTP transfers.
-24 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -25 """ -26 Initializes the class attributes for a database connection. -27 The actual connection is established in the __enter__ method. -28 -29 Args: -30 db_user (str): The user ID for the database connection. -31 db_password (str): The password for the database user. -32 db_host (str): The system/host name for the database connection. -33 db_driver (str): The ODBC driver to be used. -34 """ -35 self.db_user = db_user -36 self.db_host = db_host -37 self.db_driver = db_driver -38 self.db_password = db_password -
Initializes the class attributes for a database connection. -The actual connection is established in the __enter__ method.
- -Args: - db_user (str): The user ID for the database connection. - db_password (str): The password for the database user. - db_host (str): The system/host name for the database connection. - db_driver (str): The ODBC driver to be used.
-75 def iclose(self): -76 """ -77 A helper method to close the connection, also useful for manual closure. -78 """ -79 if self.conn and not self.conn.closed: -80 self.conn.close() -81 pass -
A helper method to close the connection, also useful for manual closure.
-1from os.path import join - 2import paramiko - 3import pyodbc - 4import json - 5from datetime import datetime, date - 6from decimal import Decimal - 7from .getUserInfoForUser import * - 8from .sendMSG import * - 9 -10class User(getUserInfoForUser, sendMSG): -11 """ -12 A class to manage User on IBMi System -13 -14 It provides methods to connect to the system via pyodbc for SQL and -15 paramiko for SFTP transfers. -16 """ -17 -18 # ------------------------------------------------------ -19 # __init__ - initzialise the class -20 # ------------------------------------------------------ -21 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -22 """ -23 Initializes the class attributes for a database connection. -24 The actual connection is established in the __enter__ method. -25 -26 Args: -27 db_user (str): The user ID for the database connection. -28 db_password (str): The password for the database user. -29 db_host (str): The system/host name for the database connection. -30 db_driver (str): The ODBC driver to be used. -31 """ -32 self.db_user = db_user -33 self.db_host = db_host -34 self.db_driver = db_driver -35 self.db_password = db_password -36 -37 # ------------------------------------------------------ -38 # __enter__ - enter to the class -39 # ------------------------------------------------------ -40 def __enter__(self) -> 'User': -41 """ -42 Establishes the database connection when entering a 'with' block. -43 """ -44 try: -45 conn_str = ( -46 f"DRIVER={self.db_driver};" -47 f"SYSTEM={self.db_host};" -48 f"UID={self.db_user};" -49 f"PWD={self.db_password};" -50 ) -51 self.conn = pyodbc.connect(conn_str, autocommit=True) -52 return self -53 except pyodbc.Error as ex: -54 sqlstate = ex.args[0] -55 print(f"Database connection failed with error: {sqlstate}") -56 raise -57 -58 # ------------------------------------------------------ -59 # __exit__ - leave the class -60 # ------------------------------------------------------ -61 def __exit__(self, exc_type, exc_val, exc_tb): -62 """ -63 Closes the database connection when exiting a 'with' block. -64 This method is called automatically, even if an error occurred. -65 """ -66 self.iclose() -67 -68 # ------------------------------------------------------ -69 # iClose - close connection -70 # ------------------------------------------------------ -71 def iclose(self): -72 """ -73 A helper method to close the connection, also useful for manual closure. -74 """ -75 if self.conn and not self.conn.closed: -76 self.conn.close() -77 pass -
11class User(getUserInfoForUser, sendMSG): -12 """ -13 A class to manage User on IBMi System -14 -15 It provides methods to connect to the system via pyodbc for SQL and -16 paramiko for SFTP transfers. -17 """ -18 -19 # ------------------------------------------------------ -20 # __init__ - initzialise the class -21 # ------------------------------------------------------ -22 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -23 """ -24 Initializes the class attributes for a database connection. -25 The actual connection is established in the __enter__ method. -26 -27 Args: -28 db_user (str): The user ID for the database connection. -29 db_password (str): The password for the database user. -30 db_host (str): The system/host name for the database connection. -31 db_driver (str): The ODBC driver to be used. -32 """ -33 self.db_user = db_user -34 self.db_host = db_host -35 self.db_driver = db_driver -36 self.db_password = db_password -37 -38 # ------------------------------------------------------ -39 # __enter__ - enter to the class -40 # ------------------------------------------------------ -41 def __enter__(self) -> 'User': -42 """ -43 Establishes the database connection when entering a 'with' block. -44 """ -45 try: -46 conn_str = ( -47 f"DRIVER={self.db_driver};" -48 f"SYSTEM={self.db_host};" -49 f"UID={self.db_user};" -50 f"PWD={self.db_password};" -51 ) -52 self.conn = pyodbc.connect(conn_str, autocommit=True) -53 return self -54 except pyodbc.Error as ex: -55 sqlstate = ex.args[0] -56 print(f"Database connection failed with error: {sqlstate}") -57 raise -58 -59 # ------------------------------------------------------ -60 # __exit__ - leave the class -61 # ------------------------------------------------------ -62 def __exit__(self, exc_type, exc_val, exc_tb): -63 """ -64 Closes the database connection when exiting a 'with' block. -65 This method is called automatically, even if an error occurred. -66 """ -67 self.iclose() -68 -69 # ------------------------------------------------------ -70 # iClose - close connection -71 # ------------------------------------------------------ -72 def iclose(self): -73 """ -74 A helper method to close the connection, also useful for manual closure. -75 """ -76 if self.conn and not self.conn.closed: -77 self.conn.close() -78 pass -
A class to manage User on IBMi System
- -It provides methods to connect to the system via pyodbc for SQL and -paramiko for SFTP transfers.
-22 def __init__(self, db_user: str, db_password: str, db_host: str, db_driver: str): -23 """ -24 Initializes the class attributes for a database connection. -25 The actual connection is established in the __enter__ method. -26 -27 Args: -28 db_user (str): The user ID for the database connection. -29 db_password (str): The password for the database user. -30 db_host (str): The system/host name for the database connection. -31 db_driver (str): The ODBC driver to be used. -32 """ -33 self.db_user = db_user -34 self.db_host = db_host -35 self.db_driver = db_driver -36 self.db_password = db_password -
Initializes the class attributes for a database connection. -The actual connection is established in the __enter__ method.
- -Args: - db_user (str): The user ID for the database connection. - db_password (str): The password for the database user. - db_host (str): The system/host name for the database connection. - db_driver (str): The ODBC driver to be used.
-72 def iclose(self): -73 """ -74 A helper method to close the connection, also useful for manual closure. -75 """ -76 if self.conn and not self.conn.closed: -77 self.conn.close() -78 pass -
A helper method to close the connection, also useful for manual closure.
-1import json - 2from datetime import datetime, date - 3from decimal import Decimal - 4 - 5 - 6class getInfoForLibrary: - 7 def __init__(self, connection): - 8 self.conn = connection - 9 -10 def _convert_to_json_ready(self, row, description): -11 """Interne Hilfsmethode zur Typ-Konvertierung und Bereinigung.""" -12 row_dict = {} -13 titles = [col[0] for col in description] -14 -15 for i, value in enumerate(row): -16 key = titles[i] -17 # Typ-Prüfung für JSON-Serialisierung -18 if isinstance(value, (datetime, date)): -19 row_dict[key] = value.isoformat() -20 elif isinstance(value, Decimal): -21 row_dict[key] = float(value) -22 elif isinstance(value, bytes): -23 row_dict[key] = value.decode('utf-8', errors='replace') -24 elif value is None: -25 row_dict[key] = None -26 else: -27 # Entfernt unnötige Leerzeichen von CHAR-Feldern -28 row_dict[key] = str(value).strip() -29 return row_dict -30 -31 def getLibraryInfo(self, library: str, wantJson=True): -32 if not library or len(library) > 10: -33 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") -34 -35 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" -36 try: -37 with self.conn.cursor() as cursor: -38 cursor.execute(sql_query) -39 row = cursor.fetchone() -40 -41 if not row: -42 error_msg = {"error": f"No data found for library: {library}"} -43 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) -44 -45 if wantJson: -46 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) -47 -48 return row -49 except Exception as e: -50 print(f"Fehler bei getLibraryInfo: {e}") -51 return None -52 -53 def getFileInfo(self, library: str, qFiles: bool = False) -> str: -54 if not library: -55 return json.dumps([{"error": "A library name is required."}]) -56 -57 if qFiles: -58 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" -59 else: -60 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" -61 -62 try: -63 with self.conn.cursor() as cursor: -64 cursor.execute(sql) -65 rows = cursor.fetchall() -66 -67 if not rows: -68 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) -69 -70 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -71 self.conn.commit() -72 return json.dumps(result_list, indent=4) -73 except Exception as e: -74 if self.conn: self.conn.rollback() -75 return json.dumps([{"error": f"Database Error: {str(e)}"}]) -76 -77 def getAllLibraries(self): -78 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste -79 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" -80 try: -81 with self.conn.cursor() as cursor: -82 cursor.execute(sql) -83 rows = cursor.fetchall() -84 -85 if not rows: -86 return json.dumps([{"error": "No Libraries found"}]) -87 -88 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -89 self.conn.commit() -90 return json.dumps(result_list, indent=4) -91 except Exception as e: -92 print(f"Fehler bei getAllLibraries: {e}") -93 if self.conn: self.conn.rollback() -94 return False -
7class getInfoForLibrary: - 8 def __init__(self, connection): - 9 self.conn = connection -10 -11 def _convert_to_json_ready(self, row, description): -12 """Interne Hilfsmethode zur Typ-Konvertierung und Bereinigung.""" -13 row_dict = {} -14 titles = [col[0] for col in description] -15 -16 for i, value in enumerate(row): -17 key = titles[i] -18 # Typ-Prüfung für JSON-Serialisierung -19 if isinstance(value, (datetime, date)): -20 row_dict[key] = value.isoformat() -21 elif isinstance(value, Decimal): -22 row_dict[key] = float(value) -23 elif isinstance(value, bytes): -24 row_dict[key] = value.decode('utf-8', errors='replace') -25 elif value is None: -26 row_dict[key] = None -27 else: -28 # Entfernt unnötige Leerzeichen von CHAR-Feldern -29 row_dict[key] = str(value).strip() -30 return row_dict -31 -32 def getLibraryInfo(self, library: str, wantJson=True): -33 if not library or len(library) > 10: -34 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") -35 -36 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" -37 try: -38 with self.conn.cursor() as cursor: -39 cursor.execute(sql_query) -40 row = cursor.fetchone() -41 -42 if not row: -43 error_msg = {"error": f"No data found for library: {library}"} -44 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) -45 -46 if wantJson: -47 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) -48 -49 return row -50 except Exception as e: -51 print(f"Fehler bei getLibraryInfo: {e}") -52 return None -53 -54 def getFileInfo(self, library: str, qFiles: bool = False) -> str: -55 if not library: -56 return json.dumps([{"error": "A library name is required."}]) -57 -58 if qFiles: -59 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" -60 else: -61 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" -62 -63 try: -64 with self.conn.cursor() as cursor: -65 cursor.execute(sql) -66 rows = cursor.fetchall() -67 -68 if not rows: -69 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) -70 -71 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -72 self.conn.commit() -73 return json.dumps(result_list, indent=4) -74 except Exception as e: -75 if self.conn: self.conn.rollback() -76 return json.dumps([{"error": f"Database Error: {str(e)}"}]) -77 -78 def getAllLibraries(self): -79 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste -80 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" -81 try: -82 with self.conn.cursor() as cursor: -83 cursor.execute(sql) -84 rows = cursor.fetchall() -85 -86 if not rows: -87 return json.dumps([{"error": "No Libraries found"}]) -88 -89 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -90 self.conn.commit() -91 return json.dumps(result_list, indent=4) -92 except Exception as e: -93 print(f"Fehler bei getAllLibraries: {e}") -94 if self.conn: self.conn.rollback() -95 return False -
32 def getLibraryInfo(self, library: str, wantJson=True): -33 if not library or len(library) > 10: -34 raise ValueError("Ungültiger Bibliotheksname (max. 10 Zeichen).") -35 -36 sql_query = f"SELECT * FROM TABLE(QSYS2.LIBRARY_INFO(upper('{library}')))" -37 try: -38 with self.conn.cursor() as cursor: -39 cursor.execute(sql_query) -40 row = cursor.fetchone() -41 -42 if not row: -43 error_msg = {"error": f"No data found for library: {library}"} -44 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg["error"]) -45 -46 if wantJson: -47 return json.dumps(self._convert_to_json_ready(row, cursor.description), indent=4) -48 -49 return row -50 except Exception as e: -51 print(f"Fehler bei getLibraryInfo: {e}") -52 return None -
54 def getFileInfo(self, library: str, qFiles: bool = False) -> str: -55 if not library: -56 return json.dumps([{"error": "A library name is required."}]) -57 -58 if qFiles: -59 sql = f"SELECT * FROM QSYS2.SYSMEMBERSTAT WHERE SYSTEM_TABLE_SCHEMA = '{library.upper()}' AND SOURCE_TYPE IS NOT NULL ORDER BY SYSTEM_TABLE_MEMBER" -60 else: -61 sql = f"SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('{library.upper()}', '*ALL')) AS X" -62 -63 try: -64 with self.conn.cursor() as cursor: -65 cursor.execute(sql) -66 rows = cursor.fetchall() -67 -68 if not rows: -69 return json.dumps([{"error": f"No Files Found in Library: {library}"}]) -70 -71 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -72 self.conn.commit() -73 return json.dumps(result_list, indent=4) -74 except Exception as e: -75 if self.conn: self.conn.rollback() -76 return json.dumps([{"error": f"Database Error: {str(e)}"}]) -
78 def getAllLibraries(self): -79 # Hier nutzen wir nun auch die dynamische Spaltenerkennung statt der harten Liste -80 sql = "SELECT * FROM TABLE(QSYS2.OBJECT_STATISTICS('*ALL', '*LIB')) AS X" -81 try: -82 with self.conn.cursor() as cursor: -83 cursor.execute(sql) -84 rows = cursor.fetchall() -85 -86 if not rows: -87 return json.dumps([{"error": "No Libraries found"}]) -88 -89 result_list = [self._convert_to_json_ready(row, cursor.description) for row in rows] -90 self.conn.commit() -91 return json.dumps(result_list, indent=4) -92 except Exception as e: -93 print(f"Fehler bei getAllLibraries: {e}") -94 if self.conn: self.conn.rollback() -95 return False -
1from os.path import join - 2import paramiko - 3import pyodbc - 4import json - 5from datetime import datetime, date - 6from decimal import Decimal - 7 - 8class getUserInfoForUser(): - 9 """ - 10 Handles user information retrieval and messaging functionalities. - 11 - 12 This class provides methods to interact with the database for retrieving user information - 13 and to send messages to specified users. It supports data retrieval in different formats - 14 (e.g., JSON or tuple), and it enables system messaging with configurable options. - 15 - 16 :ivar conn: Database connection object used for executing queries. - 17 :type conn: Any - 18 """ - 19 def getAllUsers(self, wantJson: bool = False): - 20 """ - 21 Retrieves all user information from the database. Optionally returns the data in - 22 JSON format depending on the provided parameter. - 23 - 24 Retrieves a list of users stored in the database and can output the data either as - 25 a list of tuples or in JSON format. The query fetches all fields available in the - 26 user information database table and handles cases where no data is found. - 27 - 28 :param wantJson: Boolean flag to indicate whether the result should be returned - 29 in JSON format. If set to False, the result will be a list of tuples. Default - 30 is False. - 31 :return: The data fetched from the database. When `wantJson` is True, returns a - 32 JSON object as a string. Otherwise, returns a list of tuples. - 33 """ - 34 sql_query = "SELECT * FROM qsys2.user_info" - 35 - 36 def json_serial(obj): - 37 if hasattr(obj, 'isoformat'): - 38 return obj.isoformat() - 39 return str(obj) - 40 - 41 try: - 42 with self.conn.cursor() as cursor: - 43 cursor.execute(sql_query) - 44 rows = cursor.fetchall() - 45 - 46 if not rows: - 47 error_msg = {'error': 'No data found'} - 48 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] - 49 - 50 # Get column names - 51 columns = [column[0] for column in cursor.description] - 52 - 53 if wantJson: - 54 # Create a LIST of dictionaries - 55 results = [dict(zip(columns, r)) for r in rows] - 56 return json.dumps(results, indent=4, default=json_serial) - 57 - 58 return rows # Returns the list of tuples - 59 - 60 except Exception as e: - 61 print(f"An error occurred: {e}") - 62 return None - 63 - 64 def getSingleUserInformation(self, username: str, wantJson: bool = False): - 65 """ - 66 Retrieves information about a specific user from the database based on their username. The function supports - 67 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. - 68 - 69 :param username: The username of the database user whose information is to be retrieved. Must not be empty. - 70 :type username: str - 71 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. - 72 :type wantJson: bool - 73 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. - 74 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the - 75 value of `wantJson`. Returns None if an exception occurs. - 76 :rtype: Union[tuple, str, None] - 77 :raises ValueError: If the `username` input is empty or None. - 78 """ - 79 if not username: - 80 raise ValueError("A username is required.") - 81 - 82 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" - 83 - 84 def json_serial(obj): - 85 # Handle datetime and Decimal (common in DB2) - 86 if hasattr(obj, 'isoformat'): - 87 return obj.isoformat() - 88 return str(obj) - 89 - 90 try: - 91 with self.conn.cursor() as cursor: - 92 cursor.execute(sql_query) - 93 row = cursor.fetchone() # Since you only expect one user - 94 - 95 if not row: - 96 error_msg = {'error': 'No data found for User: ' + username} - 97 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) - 98 - 99 # DYNAMICALLY get column names from the database itself -100 columns = [column[0] for column in cursor.description] -101 row_dict = dict(zip(columns, row)) -102 -103 if wantJson: -104 return json.dumps(row_dict, indent=4, default=json_serial) -105 return row # Returns the tuple -106 -107 except Exception as e: -108 print(f"An error occurred: {e}") -109 return None -
9class getUserInfoForUser(): - 10 """ - 11 Handles user information retrieval and messaging functionalities. - 12 - 13 This class provides methods to interact with the database for retrieving user information - 14 and to send messages to specified users. It supports data retrieval in different formats - 15 (e.g., JSON or tuple), and it enables system messaging with configurable options. - 16 - 17 :ivar conn: Database connection object used for executing queries. - 18 :type conn: Any - 19 """ - 20 def getAllUsers(self, wantJson: bool = False): - 21 """ - 22 Retrieves all user information from the database. Optionally returns the data in - 23 JSON format depending on the provided parameter. - 24 - 25 Retrieves a list of users stored in the database and can output the data either as - 26 a list of tuples or in JSON format. The query fetches all fields available in the - 27 user information database table and handles cases where no data is found. - 28 - 29 :param wantJson: Boolean flag to indicate whether the result should be returned - 30 in JSON format. If set to False, the result will be a list of tuples. Default - 31 is False. - 32 :return: The data fetched from the database. When `wantJson` is True, returns a - 33 JSON object as a string. Otherwise, returns a list of tuples. - 34 """ - 35 sql_query = "SELECT * FROM qsys2.user_info" - 36 - 37 def json_serial(obj): - 38 if hasattr(obj, 'isoformat'): - 39 return obj.isoformat() - 40 return str(obj) - 41 - 42 try: - 43 with self.conn.cursor() as cursor: - 44 cursor.execute(sql_query) - 45 rows = cursor.fetchall() - 46 - 47 if not rows: - 48 error_msg = {'error': 'No data found'} - 49 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] - 50 - 51 # Get column names - 52 columns = [column[0] for column in cursor.description] - 53 - 54 if wantJson: - 55 # Create a LIST of dictionaries - 56 results = [dict(zip(columns, r)) for r in rows] - 57 return json.dumps(results, indent=4, default=json_serial) - 58 - 59 return rows # Returns the list of tuples - 60 - 61 except Exception as e: - 62 print(f"An error occurred: {e}") - 63 return None - 64 - 65 def getSingleUserInformation(self, username: str, wantJson: bool = False): - 66 """ - 67 Retrieves information about a specific user from the database based on their username. The function supports - 68 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. - 69 - 70 :param username: The username of the database user whose information is to be retrieved. Must not be empty. - 71 :type username: str - 72 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. - 73 :type wantJson: bool - 74 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. - 75 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the - 76 value of `wantJson`. Returns None if an exception occurs. - 77 :rtype: Union[tuple, str, None] - 78 :raises ValueError: If the `username` input is empty or None. - 79 """ - 80 if not username: - 81 raise ValueError("A username is required.") - 82 - 83 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" - 84 - 85 def json_serial(obj): - 86 # Handle datetime and Decimal (common in DB2) - 87 if hasattr(obj, 'isoformat'): - 88 return obj.isoformat() - 89 return str(obj) - 90 - 91 try: - 92 with self.conn.cursor() as cursor: - 93 cursor.execute(sql_query) - 94 row = cursor.fetchone() # Since you only expect one user - 95 - 96 if not row: - 97 error_msg = {'error': 'No data found for User: ' + username} - 98 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) - 99 -100 # DYNAMICALLY get column names from the database itself -101 columns = [column[0] for column in cursor.description] -102 row_dict = dict(zip(columns, row)) -103 -104 if wantJson: -105 return json.dumps(row_dict, indent=4, default=json_serial) -106 return row # Returns the tuple -107 -108 except Exception as e: -109 print(f"An error occurred: {e}") -110 return None -
Handles user information retrieval and messaging functionalities.
- -This class provides methods to interact with the database for retrieving user information -and to send messages to specified users. It supports data retrieval in different formats -(e.g., JSON or tuple), and it enables system messaging with configurable options.
- -:ivar conn: Database connection object used for executing queries.
-20 def getAllUsers(self, wantJson: bool = False): -21 """ -22 Retrieves all user information from the database. Optionally returns the data in -23 JSON format depending on the provided parameter. -24 -25 Retrieves a list of users stored in the database and can output the data either as -26 a list of tuples or in JSON format. The query fetches all fields available in the -27 user information database table and handles cases where no data is found. -28 -29 :param wantJson: Boolean flag to indicate whether the result should be returned -30 in JSON format. If set to False, the result will be a list of tuples. Default -31 is False. -32 :return: The data fetched from the database. When `wantJson` is True, returns a -33 JSON object as a string. Otherwise, returns a list of tuples. -34 """ -35 sql_query = "SELECT * FROM qsys2.user_info" -36 -37 def json_serial(obj): -38 if hasattr(obj, 'isoformat'): -39 return obj.isoformat() -40 return str(obj) -41 -42 try: -43 with self.conn.cursor() as cursor: -44 cursor.execute(sql_query) -45 rows = cursor.fetchall() -46 -47 if not rows: -48 error_msg = {'error': 'No data found'} -49 return json.dumps(error_msg, indent=4) if wantJson else [("error", "No data found")] -50 -51 # Get column names -52 columns = [column[0] for column in cursor.description] -53 -54 if wantJson: -55 # Create a LIST of dictionaries -56 results = [dict(zip(columns, r)) for r in rows] -57 return json.dumps(results, indent=4, default=json_serial) -58 -59 return rows # Returns the list of tuples -60 -61 except Exception as e: -62 print(f"An error occurred: {e}") -63 return None -
Retrieves all user information from the database. Optionally returns the data in -JSON format depending on the provided parameter.
- -Retrieves a list of users stored in the database and can output the data either as -a list of tuples or in JSON format. The query fetches all fields available in the -user information database table and handles cases where no data is found.
- ---The data fetched from the database. When
-wantJsonis True, returns a - JSON object as a string. Otherwise, returns a list of tuples.
65 def getSingleUserInformation(self, username: str, wantJson: bool = False): - 66 """ - 67 Retrieves information about a specific user from the database based on their username. The function supports - 68 returning data either as a JSON-formatted string or as a tuple with corresponding database fields. - 69 - 70 :param username: The username of the database user whose information is to be retrieved. Must not be empty. - 71 :type username: str - 72 :param wantJson: Indicates whether the output should be formatted as JSON. Defaults to False. - 73 :type wantJson: bool - 74 :return: A tuple containing database fields if `wantJson` is False, or a JSON-formatted string if `wantJson` is True. - 75 If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the - 76 value of `wantJson`. Returns None if an exception occurs. - 77 :rtype: Union[tuple, str, None] - 78 :raises ValueError: If the `username` input is empty or None. - 79 """ - 80 if not username: - 81 raise ValueError("A username is required.") - 82 - 83 sql_query = f"SELECT * FROM qsys2.user_info WHERE AUTHORIZATION_NAME = upper('{username}')" - 84 - 85 def json_serial(obj): - 86 # Handle datetime and Decimal (common in DB2) - 87 if hasattr(obj, 'isoformat'): - 88 return obj.isoformat() - 89 return str(obj) - 90 - 91 try: - 92 with self.conn.cursor() as cursor: - 93 cursor.execute(sql_query) - 94 row = cursor.fetchone() # Since you only expect one user - 95 - 96 if not row: - 97 error_msg = {'error': 'No data found for User: ' + username} - 98 return json.dumps(error_msg, indent=4) if wantJson else ("error", error_msg['error']) - 99 -100 # DYNAMICALLY get column names from the database itself -101 columns = [column[0] for column in cursor.description] -102 row_dict = dict(zip(columns, row)) -103 -104 if wantJson: -105 return json.dumps(row_dict, indent=4, default=json_serial) -106 return row # Returns the tuple -107 -108 except Exception as e: -109 print(f"An error occurred: {e}") -110 return None -
Retrieves information about a specific user from the database based on their username. The function supports -returning data either as a JSON-formatted string or as a tuple with corresponding database fields.
- --- -A tuple containing database fields if
-wantJsonis False, or a JSON-formatted string ifwantJsonis True. - If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the - value ofwantJson. Returns None if an exception occurs.
username input is empty or None.1from _ast import Raise - 2from os.path import join - 3import paramiko - 4import pyodbc - 5import json - 6from datetime import datetime, date - 7from decimal import Decimal - 8from typing import Union - 9from pathlib import PureWindowsPath - 10 - 11 - 12class saveLibrary: - 13 - 14 def saveLibrary(self, - 15 library: str, - 16 saveFileName: str, - 17 dev: str = None, - 18 vol: str = None, - 19 toLibrary: str = None, - 20 description: str = None, - 21 localPath: str = None, - 22 remPath: str = None, - 23 getZip: bool = False, - 24 port: int = None, - 25 remSavf=True, - 26 version: str = None, - 27 max_records: Union[int, str, None] = None, - 28 asp: Union[int, str, None] = None, - 29 waitFile: Union[int, str, None] = None, - 30 share: str = None, - 31 authority: str = None - 32 ) -> bool: - 33 """ - 34 Saves a library to a specified save file, providing options for further customization such - 35 as setting the target release, saving as a zip file, specifying the device, volume, and more. - 36 - 37 :param library: The name of the library to be saved. Must be a valid library name or one of - 38 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. - 39 :type library: str - 40 :param saveFileName: The name of the save file where the library will be saved. - 41 :type saveFileName: str - 42 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. - 43 :type dev: str, optional - 44 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. - 45 :type vol: str, optional - 46 :param toLibrary: Target library where the save file will be temporarily stored. Defaults - 47 to the value of `library` if not specified. - 48 :type toLibrary: str, optional - 49 :param description: An optional description for the save file to be created. - 50 :type description: str, optional - 51 :param localPath: The local path where the save file will be downloaded if `getZip` is set - 52 to True. Must be an absolute path. - 53 :type localPath: str, optional - 54 :param remPath: The remote directory path on the target system to temporarily store the - 55 save file if `getZip` is set to True. Must be an absolute path. - 56 :type remPath: str, optional - 57 :param getZip: A flag that determines whether the save file should be archived into a zip - 58 file and downloaded locally. - 59 :type getZip: bool - 60 :param port: Specifies the port to be used for transferring the save file when `getZip` is - 61 enabled. - 62 :type port: int, optional - 63 :param remSavf: A flag indicating whether the save file should be removed from the remote - 64 target system after a successful save. - 65 :type remSavf: bool - 66 :param version: The target release version for the save operation. Valid values include - 67 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. - 68 :type version: str, optional - 69 :param max_records: Optional parameter for specifying the maximum number of records in - 70 the save file. - 71 :type max_records: Union[int, str, None], optional - 72 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. - 73 :type asp: Union[int, str, None], optional - 74 :param waitFile: The amount of time to wait for file access locks to be released. - 75 :type waitFile: Union[int, str, None], optional - 76 :param share: Specifies the share handling for threads or users accessing the save file. - 77 :type share: str, optional - 78 :param authority: Authority option to set for the save file being saved. - 79 :type authority: str, optional - 80 :return: A boolean indicating whether the library was successfully saved. Returns True on - 81 success or False on failure. - 82 :rtype: bool - 83 """ - 84 # Target Release List - 85 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", - 86 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", - 87 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", - 88 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", - 89 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] - 90 - 91 # check if something missing from the Arguments - 92 # check if Library is empty or not - 93 if not library: - 94 raise ValueError("A library name is required.") - 95 # check if saveFileName is empty or not - 96 if not saveFileName: - 97 raise ValueError("A save file name is required.") - 98 # check if toLibrary is empty or not - 99 if not toLibrary: -100 toLibrary = library -101 # check if user want the SaveFile as ZIP File -102 if getZip: -103 if not remPath: -104 raise ValueError("A remote path is required. Use 'remPath' instead.") -105 elif remPath[-1] == '/': -106 remPath = remPath[:-1] -107 if not localPath: -108 raise ValueError("A local path is required. Use 'localPath' instead.") -109 elif localPath[-1] == '/': -110 localPath = localPath[:-1] -111 # check wich Version of SaveFile is wanted -112 if not version in list(trgList): -113 version = "*CURRENT" -114 else: -115 version = version.upper() -116 command_str: str = f'SAVLIB' -117 -118 # check if Library is valid or not -119 validated_library = self.__validate_max_value(value=library, param_name='library', -120 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', -121 library]) -122 if validated_library: -123 command_str += f' LIB({validated_library})' -124 else: -125 library_str = str(library) -126 raise ValueError( -127 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") -128 # check Dev - Device -129 if not dev in ['*SAVF', '*MEDDFN']: -130 command_str += f' DEV(*SAVF)' -131 else: -132 command_str += f' DEV({dev.upper()})' -133 if vol is not None and vol == '*MOUNTED': -134 command_str += f' VOL({vol})' -135 # starting with mem main Sourcecode of saveLLibrary -136 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, -137 share=share, authority=authority): -138 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -139 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -140 #print(command_str) -141 try: -142 with self.conn.cursor() as cursor: -143 # execute the Command for creating a Savefile. -144 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -145 if getZip: -146 try: -147 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') -148 -149 destination_local_path = join(localPath, saveFileName.upper() + '.savf') -150 command_str = ( -151 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " -152 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" -153 ) -154 -155 # Execute the command on the remote system -156 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) -157 -158 if self.__getSavFile(localFilePath=destination_local_path, -159 remotePath=remote_temp_savf_path, port=port): -160 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" -161 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) -162 else: -163 raise ValueError("Something went wrong. With downloading the Save File.") -164 if remSavf: -165 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): -166 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") -167 -168 except Exception as e: -169 self.__handle_error(error=e, pgm="saveLibrary - Transfer") -170 -171 except Exception as e: -172 self.__handle_error(error=e, pgm="saveLibrary") -173 self.conn.rollback() -174 return False -175 else: -176 self.conn.commit() -177 if getZip: -178 print(f"File successfully downloaded to: {destination_local_path}") -179 return True -180 -181 print(f"Successfully saved in the Library '{library}' successfully.") -182 return True -183 -184 return False -185 -186 # ------------------------------------------------------ -187 # sub Function: create the Savefile on the AS400 -188 # ------------------------------------------------------ -189 def __crtsavf(self, -190 saveFileName: str, -191 library: str, -192 description: str = None, -193 max_records: Union[int, str, None] = None, -194 asp: Union[int, str, None] = None, -195 waitFile: Union[int, str, None] = None, -196 share: str = None, -197 authority: str = None -198 ) -> bool: -199 """ -200 Sub-function to create a save file on the IBM i server. -201 -202 This function executes the `CRTSAVF` (Create Save File) CL command -203 to create a new save file in the specified library. This is a -204 prerequisite for saving a library's contents. -205 -206 Args: -207 saveFileName (str): The name of the save file to be created. -208 This will be the AS/400 object name. -209 library (str): The name of the library where the save file will be created. -210 description (str, optional): A text description for the save file. Defaults to None. -211 -212 Returns: -213 bool: True if the save file was created successfully, False otherwise. -214 """ -215 # check is a parameter empty or not -216 -217 if not saveFileName: -218 raise ValueError("A file name is required.") -219 if not library: -220 raise ValueError("A library name is required.") -221 if not description: -222 description = 'A SaveFile from iLibrary' -223 -224 command_str: str = f"CRTSAVF FILE({library.upper().strip()}/{saveFileName.upper().strip()}) TEXT('{description.strip()}')" -225 -226 # check max_records for MAXRCDS parameter -227 if self.__validate_max_value(value=max_records, param_name='max_records', str_format=['*NOMAX'], -228 max_limit=4293525600) and not None: -229 command_str += f" MAXRCDS({max_records})" -230 # check asp for ASP 2147483647 -231 if self.__validate_max_value(value=asp, param_name='asp', str_format=['*LIBASP'], max_limit=32) and not None: -232 command_str += f" ASP({asp})" -233 if self.__validate_max_value(value=waitFile, param_name='waitFile', str_format=['*IMMED', '*CLS'], -234 max_limit=2147483647) and not None: -235 command_str += f" WAITFILE({waitFile})" -236 if self.__validate_max_value(value=share, param_name='share', str_format=['*YES', '*NO']) and not None: -237 command_str += f" SHARE({share})" -238 -239 if authority is not None: -240 upper_authority = authority.upper() -241 -242 # 1. Check for custom authority (not in list AND up to 10 chars) -243 if upper_authority not in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE'] and len( -244 upper_authority) <= 10: -245 # **CORRECTION 1: Use upper_authority here, not the undefined 'auth'** -246 command_str += f" AUT({upper_authority})" -247 # The 'pass' statements are redundant and can be removed -248 -249 # 2. Add an 'elif' to handle the case where it IS one of the standard values -250 elif upper_authority in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE']: -251 command_str += f" AUT({upper_authority})" -252 -253 -254 try: -255 with self.conn.cursor() as cursor: -256 # execute the Command for creating a Savefile. -257 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -258 -259 except Exception as e: -260 self.__handle_error(error=e, pgm="__crtsavf") -261 # remove a SAVF if its exists and we got an error -262 if e.args[0] == 'HY000': -263 sql = """ -264 SELECT 1 -265 FROM QSYS2.SAVE_FILE_INFO -266 WHERE SAVE_FILE_LIBRARY = ? \ -267 AND SAVE_FILE = ? -268 FETCH FIRST 1 ROW ONLY \ -269 """ -270 cursor = self.conn.cursor() -271 cursor.execute(sql, library, saveFileName) -272 result = cursor.fetchone() -273 if result is not None: -274 self.removeFile(library=library, saveFileName=saveFileName) -275 self.conn.rollback() -276 raise ValueError(e) -277 else: -278 self.conn.commit() -279 return True -280 -281 # -------------------------------------------------------------------------- -282 # __validate_max_value - Helper Function for checking parameter -283 # -------------------------------------------------------------------------- -284 def __validate_max_value(self, -285 value: Union[int, str, None], -286 param_name: str, -287 str_format: list[str], -288 min_limit: int = 1, -289 max_limit: int = None -290 ) -> Union[int, str, bool]: # Includes bool as requested -291 """ -292 Validates an input value for 'MAX' type parameters against a custom range. -293 Handles special strings defined in str_format and numeric values. -294 -295 Returns: The validated integer, the standardized special string, or False on failure (if no exception is raised). -296 Raises: ValueError for invalid string format or out-of-range number. -297 """ -298 -299 # Helper for clear error messages -300 str_options = ", ".join([f"'{s}'" for s in str_format]) -301 -302 # 1. Handle special string -303 if isinstance(value, str): -304 upper_value = value.upper() -305 -306 for special_value in str_format: -307 normalized_special_value = special_value.upper() -308 -309 if upper_value == special_value.upper() or upper_value == normalized_special_value: -310 # Found a match! Return the official, fully formatted string. -311 return special_value -312 -313 # 2. Attempt Numeric Conversion (handles int and string-of-int) -314 if value is not None: -315 try: -316 numeric_value = int(value) -317 except ValueError: -318 # Value is an invalid string (e.g., 'hello') -319 raise ValueError( -320 f"Invalid value for {param_name}. Must be '{str_format}' or a number " -321 f"between {min_limit} and {max_limit:,}." -322 ) -323 else: -324 # If the value is None -325 return False -326 -327 # 3. Check Numeric Range -328 if min_limit <= numeric_value <= max_limit: -329 return numeric_value -330 else: -331 # Number is out of range -332 raise ValueError( -333 f"Invalid numeric value for {param_name}. Must be between {min_limit} and {max_limit:,}. " -334 f"Received: {numeric_value}" -335 ) -336 -337 # ------------------------------------------------------ -338 # getZipFile - getting the Zipfile from the SaveFile -339 # ------------------------------------------------------ -340 def __getSavFile(self, -341 localFilePath: str, -342 remotePath: str, -343 port: int = None -344 ) -> bool: -345 """ -346 Downloads a file from the remote IBM i via SFTP. -347 -348 This method uses Paramiko to establish a secure shell (SSH) connection and -349 then an SFTP session to transfer a file from a specified remote location -350 on the IBM i's IFS to a local path. -351 -352 Args: -353 localFilePath (str): The full path to the file on the remote IBM i's IFS. -354 remotePath (str): The full path on the local machine where the file -355 will be saved. For example, '/Users/user/Documents/somefile.savf'. -356 port (int, optional): The port to connect to the IBMi server. Defaults to None. -357 -358 Returns: -359 bool: True if the file was downloaded successfully, False otherwise. -360 -361 Raises: -362 ValueError: If either the remote_file_path or local_save_path is not provided. -363 """ -364 if not localFilePath: -365 print("Error: A local file path is required.") -366 return False -367 if not remotePath: -368 print("Error: A remote path is required.") -369 return False -370 if not port: -371 port = 2222 -372 -373 remotePath = PureWindowsPath(remotePath).as_posix() -374 ssh_client = paramiko.SSHClient() -375 -376 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -377 -378 try: -379 with ssh_client: -380 ssh_client.connect( -381 hostname=self.db_host, -382 username=self.db_user, -383 password=self.db_password, -384 port=port -385 ) -386 with ssh_client.open_sftp() as ftp_client: -387 ftp_client.get(remotePath, localFilePath) -388 return True -389 -390 except paramiko.ssh_exception.AuthenticationException as e: -391 print(f"Authentication failed. Check your username and password: {e}") -392 return False -393 except paramiko.ssh_exception.SSHException as e: -394 print(f"SSH error occurred: {e}") -395 return False -396 except FileNotFoundError as e: -397 print(f"File not found on the remote host: {e}") -398 return False -399 -400 finally: -401 pass -402 -403 def removeFile(self, library: str, saveFileName: str) -> bool: -404 """ -405 Removes a save file from the specified library. -406 -407 This function executes the system command to delete a save file from an IBM i -408 system. It connects to the database through a cursor, and attempts to perform -409 the operation. If an error is encountered during execution, the function -410 rolls back the transaction and logs the error. On success, the transaction -411 is committed. -412 -413 :param library: The name of the library containing the save file to be removed. -414 :type library: str -415 :param saveFileName: The name of the save file to be removed. -416 :type saveFileName: str -417 :return: True if the save file is removed successfully, otherwise False. -418 :rtype: bool -419 """ -420 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" -421 try: -422 with self.conn.cursor() as cursor: -423 # execute the Command for deleting a Savefile. -424 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -425 -426 except Exception as e: -427 self.__handle_error(error=e, pgm="removeFile") -428 self.conn.rollback() -429 return False -430 else: -431 self.conn.commit() -432 return True -433 -434 def __handle_error(self, error, pgm: str): -435 """ -436 Handles errors encountered during the execution of a command. -437 -438 This method processes an error raised during the execution of a command in a -439 specific function and extracts detailed error information including SQLSTATE -440 and the error message. The formatted details are printed to the console for -441 debugging purposes. -442 -443 :param error: The error object encountered during command execution. -444 :type error: Exception -445 :param pgm: The name of the function where the error occurred. -446 :type pgm: str -447 :return: None -448 """ -449 print("-------------------------------------------------------------") -450 print(f"An error occurred while executing command in function {pgm}:") -451 sqlstate = error.args[0] -452 error_message = error.args[1] -453 -454 print(f"SQLSTATE: {sqlstate}") -455 print(f"Message: {error_message}") -
13class saveLibrary: - 14 - 15 def saveLibrary(self, - 16 library: str, - 17 saveFileName: str, - 18 dev: str = None, - 19 vol: str = None, - 20 toLibrary: str = None, - 21 description: str = None, - 22 localPath: str = None, - 23 remPath: str = None, - 24 getZip: bool = False, - 25 port: int = None, - 26 remSavf=True, - 27 version: str = None, - 28 max_records: Union[int, str, None] = None, - 29 asp: Union[int, str, None] = None, - 30 waitFile: Union[int, str, None] = None, - 31 share: str = None, - 32 authority: str = None - 33 ) -> bool: - 34 """ - 35 Saves a library to a specified save file, providing options for further customization such - 36 as setting the target release, saving as a zip file, specifying the device, volume, and more. - 37 - 38 :param library: The name of the library to be saved. Must be a valid library name or one of - 39 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. - 40 :type library: str - 41 :param saveFileName: The name of the save file where the library will be saved. - 42 :type saveFileName: str - 43 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. - 44 :type dev: str, optional - 45 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. - 46 :type vol: str, optional - 47 :param toLibrary: Target library where the save file will be temporarily stored. Defaults - 48 to the value of `library` if not specified. - 49 :type toLibrary: str, optional - 50 :param description: An optional description for the save file to be created. - 51 :type description: str, optional - 52 :param localPath: The local path where the save file will be downloaded if `getZip` is set - 53 to True. Must be an absolute path. - 54 :type localPath: str, optional - 55 :param remPath: The remote directory path on the target system to temporarily store the - 56 save file if `getZip` is set to True. Must be an absolute path. - 57 :type remPath: str, optional - 58 :param getZip: A flag that determines whether the save file should be archived into a zip - 59 file and downloaded locally. - 60 :type getZip: bool - 61 :param port: Specifies the port to be used for transferring the save file when `getZip` is - 62 enabled. - 63 :type port: int, optional - 64 :param remSavf: A flag indicating whether the save file should be removed from the remote - 65 target system after a successful save. - 66 :type remSavf: bool - 67 :param version: The target release version for the save operation. Valid values include - 68 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. - 69 :type version: str, optional - 70 :param max_records: Optional parameter for specifying the maximum number of records in - 71 the save file. - 72 :type max_records: Union[int, str, None], optional - 73 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. - 74 :type asp: Union[int, str, None], optional - 75 :param waitFile: The amount of time to wait for file access locks to be released. - 76 :type waitFile: Union[int, str, None], optional - 77 :param share: Specifies the share handling for threads or users accessing the save file. - 78 :type share: str, optional - 79 :param authority: Authority option to set for the save file being saved. - 80 :type authority: str, optional - 81 :return: A boolean indicating whether the library was successfully saved. Returns True on - 82 success or False on failure. - 83 :rtype: bool - 84 """ - 85 # Target Release List - 86 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", - 87 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", - 88 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", - 89 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", - 90 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] - 91 - 92 # check if something missing from the Arguments - 93 # check if Library is empty or not - 94 if not library: - 95 raise ValueError("A library name is required.") - 96 # check if saveFileName is empty or not - 97 if not saveFileName: - 98 raise ValueError("A save file name is required.") - 99 # check if toLibrary is empty or not -100 if not toLibrary: -101 toLibrary = library -102 # check if user want the SaveFile as ZIP File -103 if getZip: -104 if not remPath: -105 raise ValueError("A remote path is required. Use 'remPath' instead.") -106 elif remPath[-1] == '/': -107 remPath = remPath[:-1] -108 if not localPath: -109 raise ValueError("A local path is required. Use 'localPath' instead.") -110 elif localPath[-1] == '/': -111 localPath = localPath[:-1] -112 # check wich Version of SaveFile is wanted -113 if not version in list(trgList): -114 version = "*CURRENT" -115 else: -116 version = version.upper() -117 command_str: str = f'SAVLIB' -118 -119 # check if Library is valid or not -120 validated_library = self.__validate_max_value(value=library, param_name='library', -121 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', -122 library]) -123 if validated_library: -124 command_str += f' LIB({validated_library})' -125 else: -126 library_str = str(library) -127 raise ValueError( -128 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") -129 # check Dev - Device -130 if not dev in ['*SAVF', '*MEDDFN']: -131 command_str += f' DEV(*SAVF)' -132 else: -133 command_str += f' DEV({dev.upper()})' -134 if vol is not None and vol == '*MOUNTED': -135 command_str += f' VOL({vol})' -136 # starting with mem main Sourcecode of saveLLibrary -137 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, -138 share=share, authority=authority): -139 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -140 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -141 #print(command_str) -142 try: -143 with self.conn.cursor() as cursor: -144 # execute the Command for creating a Savefile. -145 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -146 if getZip: -147 try: -148 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') -149 -150 destination_local_path = join(localPath, saveFileName.upper() + '.savf') -151 command_str = ( -152 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " -153 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" -154 ) -155 -156 # Execute the command on the remote system -157 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) -158 -159 if self.__getSavFile(localFilePath=destination_local_path, -160 remotePath=remote_temp_savf_path, port=port): -161 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" -162 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) -163 else: -164 raise ValueError("Something went wrong. With downloading the Save File.") -165 if remSavf: -166 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): -167 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") -168 -169 except Exception as e: -170 self.__handle_error(error=e, pgm="saveLibrary - Transfer") -171 -172 except Exception as e: -173 self.__handle_error(error=e, pgm="saveLibrary") -174 self.conn.rollback() -175 return False -176 else: -177 self.conn.commit() -178 if getZip: -179 print(f"File successfully downloaded to: {destination_local_path}") -180 return True -181 -182 print(f"Successfully saved in the Library '{library}' successfully.") -183 return True -184 -185 return False -186 -187 # ------------------------------------------------------ -188 # sub Function: create the Savefile on the AS400 -189 # ------------------------------------------------------ -190 def __crtsavf(self, -191 saveFileName: str, -192 library: str, -193 description: str = None, -194 max_records: Union[int, str, None] = None, -195 asp: Union[int, str, None] = None, -196 waitFile: Union[int, str, None] = None, -197 share: str = None, -198 authority: str = None -199 ) -> bool: -200 """ -201 Sub-function to create a save file on the IBM i server. -202 -203 This function executes the `CRTSAVF` (Create Save File) CL command -204 to create a new save file in the specified library. This is a -205 prerequisite for saving a library's contents. -206 -207 Args: -208 saveFileName (str): The name of the save file to be created. -209 This will be the AS/400 object name. -210 library (str): The name of the library where the save file will be created. -211 description (str, optional): A text description for the save file. Defaults to None. -212 -213 Returns: -214 bool: True if the save file was created successfully, False otherwise. -215 """ -216 # check is a parameter empty or not -217 -218 if not saveFileName: -219 raise ValueError("A file name is required.") -220 if not library: -221 raise ValueError("A library name is required.") -222 if not description: -223 description = 'A SaveFile from iLibrary' -224 -225 command_str: str = f"CRTSAVF FILE({library.upper().strip()}/{saveFileName.upper().strip()}) TEXT('{description.strip()}')" -226 -227 # check max_records for MAXRCDS parameter -228 if self.__validate_max_value(value=max_records, param_name='max_records', str_format=['*NOMAX'], -229 max_limit=4293525600) and not None: -230 command_str += f" MAXRCDS({max_records})" -231 # check asp for ASP 2147483647 -232 if self.__validate_max_value(value=asp, param_name='asp', str_format=['*LIBASP'], max_limit=32) and not None: -233 command_str += f" ASP({asp})" -234 if self.__validate_max_value(value=waitFile, param_name='waitFile', str_format=['*IMMED', '*CLS'], -235 max_limit=2147483647) and not None: -236 command_str += f" WAITFILE({waitFile})" -237 if self.__validate_max_value(value=share, param_name='share', str_format=['*YES', '*NO']) and not None: -238 command_str += f" SHARE({share})" -239 -240 if authority is not None: -241 upper_authority = authority.upper() -242 -243 # 1. Check for custom authority (not in list AND up to 10 chars) -244 if upper_authority not in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE'] and len( -245 upper_authority) <= 10: -246 # **CORRECTION 1: Use upper_authority here, not the undefined 'auth'** -247 command_str += f" AUT({upper_authority})" -248 # The 'pass' statements are redundant and can be removed -249 -250 # 2. Add an 'elif' to handle the case where it IS one of the standard values -251 elif upper_authority in ['*EXCLUDE', '*ALL', '*CHANGE', '*LIBCRTAUT', '*USE']: -252 command_str += f" AUT({upper_authority})" -253 -254 -255 try: -256 with self.conn.cursor() as cursor: -257 # execute the Command for creating a Savefile. -258 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -259 -260 except Exception as e: -261 self.__handle_error(error=e, pgm="__crtsavf") -262 # remove a SAVF if its exists and we got an error -263 if e.args[0] == 'HY000': -264 sql = """ -265 SELECT 1 -266 FROM QSYS2.SAVE_FILE_INFO -267 WHERE SAVE_FILE_LIBRARY = ? \ -268 AND SAVE_FILE = ? -269 FETCH FIRST 1 ROW ONLY \ -270 """ -271 cursor = self.conn.cursor() -272 cursor.execute(sql, library, saveFileName) -273 result = cursor.fetchone() -274 if result is not None: -275 self.removeFile(library=library, saveFileName=saveFileName) -276 self.conn.rollback() -277 raise ValueError(e) -278 else: -279 self.conn.commit() -280 return True -281 -282 # -------------------------------------------------------------------------- -283 # __validate_max_value - Helper Function for checking parameter -284 # -------------------------------------------------------------------------- -285 def __validate_max_value(self, -286 value: Union[int, str, None], -287 param_name: str, -288 str_format: list[str], -289 min_limit: int = 1, -290 max_limit: int = None -291 ) -> Union[int, str, bool]: # Includes bool as requested -292 """ -293 Validates an input value for 'MAX' type parameters against a custom range. -294 Handles special strings defined in str_format and numeric values. -295 -296 Returns: The validated integer, the standardized special string, or False on failure (if no exception is raised). -297 Raises: ValueError for invalid string format or out-of-range number. -298 """ -299 -300 # Helper for clear error messages -301 str_options = ", ".join([f"'{s}'" for s in str_format]) -302 -303 # 1. Handle special string -304 if isinstance(value, str): -305 upper_value = value.upper() -306 -307 for special_value in str_format: -308 normalized_special_value = special_value.upper() -309 -310 if upper_value == special_value.upper() or upper_value == normalized_special_value: -311 # Found a match! Return the official, fully formatted string. -312 return special_value -313 -314 # 2. Attempt Numeric Conversion (handles int and string-of-int) -315 if value is not None: -316 try: -317 numeric_value = int(value) -318 except ValueError: -319 # Value is an invalid string (e.g., 'hello') -320 raise ValueError( -321 f"Invalid value for {param_name}. Must be '{str_format}' or a number " -322 f"between {min_limit} and {max_limit:,}." -323 ) -324 else: -325 # If the value is None -326 return False -327 -328 # 3. Check Numeric Range -329 if min_limit <= numeric_value <= max_limit: -330 return numeric_value -331 else: -332 # Number is out of range -333 raise ValueError( -334 f"Invalid numeric value for {param_name}. Must be between {min_limit} and {max_limit:,}. " -335 f"Received: {numeric_value}" -336 ) -337 -338 # ------------------------------------------------------ -339 # getZipFile - getting the Zipfile from the SaveFile -340 # ------------------------------------------------------ -341 def __getSavFile(self, -342 localFilePath: str, -343 remotePath: str, -344 port: int = None -345 ) -> bool: -346 """ -347 Downloads a file from the remote IBM i via SFTP. -348 -349 This method uses Paramiko to establish a secure shell (SSH) connection and -350 then an SFTP session to transfer a file from a specified remote location -351 on the IBM i's IFS to a local path. -352 -353 Args: -354 localFilePath (str): The full path to the file on the remote IBM i's IFS. -355 remotePath (str): The full path on the local machine where the file -356 will be saved. For example, '/Users/user/Documents/somefile.savf'. -357 port (int, optional): The port to connect to the IBMi server. Defaults to None. -358 -359 Returns: -360 bool: True if the file was downloaded successfully, False otherwise. -361 -362 Raises: -363 ValueError: If either the remote_file_path or local_save_path is not provided. -364 """ -365 if not localFilePath: -366 print("Error: A local file path is required.") -367 return False -368 if not remotePath: -369 print("Error: A remote path is required.") -370 return False -371 if not port: -372 port = 2222 -373 -374 remotePath = PureWindowsPath(remotePath).as_posix() -375 ssh_client = paramiko.SSHClient() -376 -377 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -378 -379 try: -380 with ssh_client: -381 ssh_client.connect( -382 hostname=self.db_host, -383 username=self.db_user, -384 password=self.db_password, -385 port=port -386 ) -387 with ssh_client.open_sftp() as ftp_client: -388 ftp_client.get(remotePath, localFilePath) -389 return True -390 -391 except paramiko.ssh_exception.AuthenticationException as e: -392 print(f"Authentication failed. Check your username and password: {e}") -393 return False -394 except paramiko.ssh_exception.SSHException as e: -395 print(f"SSH error occurred: {e}") -396 return False -397 except FileNotFoundError as e: -398 print(f"File not found on the remote host: {e}") -399 return False -400 -401 finally: -402 pass -403 -404 def removeFile(self, library: str, saveFileName: str) -> bool: -405 """ -406 Removes a save file from the specified library. -407 -408 This function executes the system command to delete a save file from an IBM i -409 system. It connects to the database through a cursor, and attempts to perform -410 the operation. If an error is encountered during execution, the function -411 rolls back the transaction and logs the error. On success, the transaction -412 is committed. -413 -414 :param library: The name of the library containing the save file to be removed. -415 :type library: str -416 :param saveFileName: The name of the save file to be removed. -417 :type saveFileName: str -418 :return: True if the save file is removed successfully, otherwise False. -419 :rtype: bool -420 """ -421 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" -422 try: -423 with self.conn.cursor() as cursor: -424 # execute the Command for deleting a Savefile. -425 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -426 -427 except Exception as e: -428 self.__handle_error(error=e, pgm="removeFile") -429 self.conn.rollback() -430 return False -431 else: -432 self.conn.commit() -433 return True -434 -435 def __handle_error(self, error, pgm: str): -436 """ -437 Handles errors encountered during the execution of a command. -438 -439 This method processes an error raised during the execution of a command in a -440 specific function and extracts detailed error information including SQLSTATE -441 and the error message. The formatted details are printed to the console for -442 debugging purposes. -443 -444 :param error: The error object encountered during command execution. -445 :type error: Exception -446 :param pgm: The name of the function where the error occurred. -447 :type pgm: str -448 :return: None -449 """ -450 print("-------------------------------------------------------------") -451 print(f"An error occurred while executing command in function {pgm}:") -452 sqlstate = error.args[0] -453 error_message = error.args[1] -454 -455 print(f"SQLSTATE: {sqlstate}") -456 print(f"Message: {error_message}") -
15 def saveLibrary(self, - 16 library: str, - 17 saveFileName: str, - 18 dev: str = None, - 19 vol: str = None, - 20 toLibrary: str = None, - 21 description: str = None, - 22 localPath: str = None, - 23 remPath: str = None, - 24 getZip: bool = False, - 25 port: int = None, - 26 remSavf=True, - 27 version: str = None, - 28 max_records: Union[int, str, None] = None, - 29 asp: Union[int, str, None] = None, - 30 waitFile: Union[int, str, None] = None, - 31 share: str = None, - 32 authority: str = None - 33 ) -> bool: - 34 """ - 35 Saves a library to a specified save file, providing options for further customization such - 36 as setting the target release, saving as a zip file, specifying the device, volume, and more. - 37 - 38 :param library: The name of the library to be saved. Must be a valid library name or one of - 39 the predefined options such as '*NONSYS', '*ALLUSR', '*IBM', etc. - 40 :type library: str - 41 :param saveFileName: The name of the save file where the library will be saved. - 42 :type saveFileName: str - 43 :param dev: The target device for the save operation. Defaults to '*SAVF' if not provided. - 44 :type dev: str, optional - 45 :param vol: Specifies the volume to be used. Use ‘*MOUNTED’ to refer to the mounted volume. - 46 :type vol: str, optional - 47 :param toLibrary: Target library where the save file will be temporarily stored. Defaults - 48 to the value of `library` if not specified. - 49 :type toLibrary: str, optional - 50 :param description: An optional description for the save file to be created. - 51 :type description: str, optional - 52 :param localPath: The local path where the save file will be downloaded if `getZip` is set - 53 to True. Must be an absolute path. - 54 :type localPath: str, optional - 55 :param remPath: The remote directory path on the target system to temporarily store the - 56 save file if `getZip` is set to True. Must be an absolute path. - 57 :type remPath: str, optional - 58 :param getZip: A flag that determines whether the save file should be archived into a zip - 59 file and downloaded locally. - 60 :type getZip: bool - 61 :param port: Specifies the port to be used for transferring the save file when `getZip` is - 62 enabled. - 63 :type port: int, optional - 64 :param remSavf: A flag indicating whether the save file should be removed from the remote - 65 target system after a successful save. - 66 :type remSavf: bool - 67 :param version: The target release version for the save operation. Valid values include - 68 ‘*CURRENT’, or specific OS versions like 'V1R1M0', 'V2R3M0', and so on. - 69 :type version: str, optional - 70 :param max_records: Optional parameter for specifying the maximum number of records in - 71 the save file. - 72 :type max_records: Union[int, str, None], optional - 73 :param asp: Auxiliary storage pool (ASP) device number or name if applicable. - 74 :type asp: Union[int, str, None], optional - 75 :param waitFile: The amount of time to wait for file access locks to be released. - 76 :type waitFile: Union[int, str, None], optional - 77 :param share: Specifies the share handling for threads or users accessing the save file. - 78 :type share: str, optional - 79 :param authority: Authority option to set for the save file being saved. - 80 :type authority: str, optional - 81 :return: A boolean indicating whether the library was successfully saved. Returns True on - 82 success or False on failure. - 83 :rtype: bool - 84 """ - 85 # Target Release List - 86 trgList: list = ["V1R1M0", "V1R1M2", "V1R2M0", "V1R3M0", "V2R1M0", "V2R1M1", - 87 "V2R2M0", "V2R3M0", "V3R0M5", "V3R1M0", "V3R2M0", "V3R6M0", - 88 "V3R7M0", "V4R1M0", "V4R2M0", "V4R3M0", "V4R4M0", "V4R5M0", - 89 "V5R1M0", "V5R2M0", "V5R3M0", "V5R4M0", "V6R1M0", "V6R1M1", - 90 "V7R1M0", "V7R2M0", "V7R3M0", "V7R4M0", "V7R5M0", "V7R6M0"] - 91 - 92 # check if something missing from the Arguments - 93 # check if Library is empty or not - 94 if not library: - 95 raise ValueError("A library name is required.") - 96 # check if saveFileName is empty or not - 97 if not saveFileName: - 98 raise ValueError("A save file name is required.") - 99 # check if toLibrary is empty or not -100 if not toLibrary: -101 toLibrary = library -102 # check if user want the SaveFile as ZIP File -103 if getZip: -104 if not remPath: -105 raise ValueError("A remote path is required. Use 'remPath' instead.") -106 elif remPath[-1] == '/': -107 remPath = remPath[:-1] -108 if not localPath: -109 raise ValueError("A local path is required. Use 'localPath' instead.") -110 elif localPath[-1] == '/': -111 localPath = localPath[:-1] -112 # check wich Version of SaveFile is wanted -113 if not version in list(trgList): -114 version = "*CURRENT" -115 else: -116 version = version.upper() -117 command_str: str = f'SAVLIB' -118 -119 # check if Library is valid or not -120 validated_library = self.__validate_max_value(value=library, param_name='library', -121 str_format=['*NONSYS', '*ALLUSR', '*IBM', '*SELECT', '*USRSPC', -122 library]) -123 if validated_library: -124 command_str += f' LIB({validated_library})' -125 else: -126 library_str = str(library) -127 raise ValueError( -128 f"The library '{library_str}' is not valid. Must be one of the specified strings or a valid number.") -129 # check Dev - Device -130 if not dev in ['*SAVF', '*MEDDFN']: -131 command_str += f' DEV(*SAVF)' -132 else: -133 command_str += f' DEV({dev.upper()})' -134 if vol is not None and vol == '*MOUNTED': -135 command_str += f' VOL({vol})' -136 # starting with mem main Sourcecode of saveLLibrary -137 if self.__crtsavf(saveFileName, toLibrary, description, max_records=max_records, asp=asp, waitFile=waitFile, -138 share=share, authority=authority): -139 # command_str: str = f"SAVLIB LIB({library.strip()}) DEV(*SAVF) SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -140 command_str += f" SAVF({toLibrary.strip()}/{saveFileName.strip()}) TGTRLS({version.strip()})" -141 #print(command_str) -142 try: -143 with self.conn.cursor() as cursor: -144 # execute the Command for creating a Savefile. -145 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -146 if getZip: -147 try: -148 remote_temp_savf_path = join(remPath, saveFileName.upper() + '.savf') -149 -150 destination_local_path = join(localPath, saveFileName.upper() + '.savf') -151 command_str = ( -152 f"CPYTOSTMF FROMMBR('/QSYS.LIB/{toLibrary.upper().strip()}.LIB/{saveFileName.upper().strip()}.FILE') " -153 f"TOSTMF('{remote_temp_savf_path.strip()}') STMFOPT(*REPLACE)" -154 ) -155 -156 # Execute the command on the remote system -157 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str,)) -158 -159 if self.__getSavFile(localFilePath=destination_local_path, -160 remotePath=remote_temp_savf_path, port=port): -161 rmvCommand = f"QSH CMD('rm -r {remote_temp_savf_path}')" -162 cursor.execute("CALL QSYS2.QCMDEXC(?)", (rmvCommand)) -163 else: -164 raise ValueError("Something went wrong. With downloading the Save File.") -165 if remSavf: -166 if not self.removeFile(library=toLibrary, saveFileName=saveFileName): -167 raise ValueError(f"The Save File {saveFileName} was not successfully removed.") -168 -169 except Exception as e: -170 self.__handle_error(error=e, pgm="saveLibrary - Transfer") -171 -172 except Exception as e: -173 self.__handle_error(error=e, pgm="saveLibrary") -174 self.conn.rollback() -175 return False -176 else: -177 self.conn.commit() -178 if getZip: -179 print(f"File successfully downloaded to: {destination_local_path}") -180 return True -181 -182 print(f"Successfully saved in the Library '{library}' successfully.") -183 return True -184 -185 return False -
Saves a library to a specified save file, providing options for further customization such -as setting the target release, saving as a zip file, specifying the device, volume, and more.
- -library if not specified.getZip is set
-to True. Must be an absolute path.getZip is set to True. Must be an absolute path.getZip is
-enabled.--A boolean indicating whether the library was successfully saved. Returns True on - success or False on failure.
-
404 def removeFile(self, library: str, saveFileName: str) -> bool: -405 """ -406 Removes a save file from the specified library. -407 -408 This function executes the system command to delete a save file from an IBM i -409 system. It connects to the database through a cursor, and attempts to perform -410 the operation. If an error is encountered during execution, the function -411 rolls back the transaction and logs the error. On success, the transaction -412 is committed. -413 -414 :param library: The name of the library containing the save file to be removed. -415 :type library: str -416 :param saveFileName: The name of the save file to be removed. -417 :type saveFileName: str -418 :return: True if the save file is removed successfully, otherwise False. -419 :rtype: bool -420 """ -421 command_str: str = f"DLTF FILE({library.upper()}/{saveFileName.upper()})" -422 try: -423 with self.conn.cursor() as cursor: -424 # execute the Command for deleting a Savefile. -425 cursor.execute("CALL QSYS2.QCMDEXC(?)", (command_str)) -426 -427 except Exception as e: -428 self.__handle_error(error=e, pgm="removeFile") -429 self.conn.rollback() -430 return False -431 else: -432 self.conn.commit() -433 return True -
Removes a save file from the specified library.
- -This function executes the system command to delete a save file from an IBM i -system. It connects to the database through a cursor, and attempts to perform -the operation. If an error is encountered during execution, the function -rolls back the transaction and logs the error. On success, the transaction -is committed.
- ---True if the save file is removed successfully, otherwise False.
-
1from os.path import join - 2import paramiko - 3import pyodbc - 4import json - 5from datetime import datetime, date - 6from decimal import Decimal - 7 - 8class sendMSG(): - 9 """ -10 Handles message-related operations by providing functionality to send messages -11 to specific users within the system. The class interacts with system APIs to -12 execute the required operations and ensures the input parameters are validated -13 before proceeding with the message sending process. -14 -15 Attributes supported by this class are not specified because the class relies -16 on method-level operations. -17 """ -18 def send_message_to_user( -19 self, -20 username: str, -21 message: str, -22 # tomsgq: str = None, -23 # msgtype: str = None, -24 # rpymsgq: str = None, -25 ccsid: int = None -26 ): -27 """ -28 Sends a message to a specified user on the system. This method interacts with the system -29 to send a message by executing an SQL query. It validates the required -30 inputs and raises an exception if they are missing. Optional parameters for -31 further message configuration can also be provided. -32 -33 :param username: The username of the recipient to whom the message will be sent. -34 :type username: str -35 :param message: The actual text message to be sent to the user. -36 :type message: str -37 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. -38 :type ccsid: int, optional -39 -40 :return: None if the message is sent successfully. -41 :rtype: None -42 -43 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. -44 -45 :raises Exception: Any other exceptions that occur during the execution of the -46 SQL query are raised, indicating issues during the process of sending the -47 message. -48 """ -49 if not username: -50 raise ValueError("Username are required.") -51 if not message: -52 raise ValueError("Message are required.") -53 -54 username = username.upper() -55 message = message.upper() -56 -57 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" -58 if ccsid: -59 sql_query += f" CCSID({ccsid})" -60 try: -61 with self.conn.cursor() as cursor: -62 cursor.execute(sql_query) -63 row_dict:dict = {"success": f'Message sent to {username}'} -64 return json.dumps(row_dict, indent=4) -65 except Exception as e: -66 row_dict: dict = {"error" : f'Error: {e}'} -67 return json.dumps(row_dict, indent=4) -
9class sendMSG(): -10 """ -11 Handles message-related operations by providing functionality to send messages -12 to specific users within the system. The class interacts with system APIs to -13 execute the required operations and ensures the input parameters are validated -14 before proceeding with the message sending process. -15 -16 Attributes supported by this class are not specified because the class relies -17 on method-level operations. -18 """ -19 def send_message_to_user( -20 self, -21 username: str, -22 message: str, -23 # tomsgq: str = None, -24 # msgtype: str = None, -25 # rpymsgq: str = None, -26 ccsid: int = None -27 ): -28 """ -29 Sends a message to a specified user on the system. This method interacts with the system -30 to send a message by executing an SQL query. It validates the required -31 inputs and raises an exception if they are missing. Optional parameters for -32 further message configuration can also be provided. -33 -34 :param username: The username of the recipient to whom the message will be sent. -35 :type username: str -36 :param message: The actual text message to be sent to the user. -37 :type message: str -38 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. -39 :type ccsid: int, optional -40 -41 :return: None if the message is sent successfully. -42 :rtype: None -43 -44 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. -45 -46 :raises Exception: Any other exceptions that occur during the execution of the -47 SQL query are raised, indicating issues during the process of sending the -48 message. -49 """ -50 if not username: -51 raise ValueError("Username are required.") -52 if not message: -53 raise ValueError("Message are required.") -54 -55 username = username.upper() -56 message = message.upper() -57 -58 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" -59 if ccsid: -60 sql_query += f" CCSID({ccsid})" -61 try: -62 with self.conn.cursor() as cursor: -63 cursor.execute(sql_query) -64 row_dict:dict = {"success": f'Message sent to {username}'} -65 return json.dumps(row_dict, indent=4) -66 except Exception as e: -67 row_dict: dict = {"error" : f'Error: {e}'} -68 return json.dumps(row_dict, indent=4) -
Handles message-related operations by providing functionality to send messages -to specific users within the system. The class interacts with system APIs to -execute the required operations and ensures the input parameters are validated -before proceeding with the message sending process.
- -Attributes supported by this class are not specified because the class relies -on method-level operations.
-19 def send_message_to_user( -20 self, -21 username: str, -22 message: str, -23 # tomsgq: str = None, -24 # msgtype: str = None, -25 # rpymsgq: str = None, -26 ccsid: int = None -27 ): -28 """ -29 Sends a message to a specified user on the system. This method interacts with the system -30 to send a message by executing an SQL query. It validates the required -31 inputs and raises an exception if they are missing. Optional parameters for -32 further message configuration can also be provided. -33 -34 :param username: The username of the recipient to whom the message will be sent. -35 :type username: str -36 :param message: The actual text message to be sent to the user. -37 :type message: str -38 :param ccsid: Optional character set identifier (CCSID) for the message. Defaults to None. -39 :type ccsid: int, optional -40 -41 :return: None if the message is sent successfully. -42 :rtype: None -43 -44 :raises ValueError: If any required parameter, such as `username` or `message`, is missing. -45 -46 :raises Exception: Any other exceptions that occur during the execution of the -47 SQL query are raised, indicating issues during the process of sending the -48 message. -49 """ -50 if not username: -51 raise ValueError("Username are required.") -52 if not message: -53 raise ValueError("Message are required.") -54 -55 username = username.upper() -56 message = message.upper() -57 -58 sql_query = f"CALL QSYS2.QCMDEXC('SNDMSG MSG(''{message}'') TOUSR({username})')" -59 if ccsid: -60 sql_query += f" CCSID({ccsid})" -61 try: -62 with self.conn.cursor() as cursor: -63 cursor.execute(sql_query) -64 row_dict:dict = {"success": f'Message sent to {username}'} -65 return json.dumps(row_dict, indent=4) -66 except Exception as e: -67 row_dict: dict = {"error" : f'Error: {e}'} -68 return json.dumps(row_dict, indent=4) -
Sends a message to a specified user on the system. This method interacts with the system -to send a message by executing an SQL query. It validates the required -inputs and raises an exception if they are missing. Optional parameters for -further message configuration can also be provided.
- --- -None if the message is sent successfully.
-
ValueError: If any required parameter, such as username or message, is missing.
Exception: Any other exceptions that occur during the execution of the -SQL query are raised, indicating issues during the process of sending the -message.
A class to manage libraries and files on an IBM i system.
\n\nIt provides methods to connect to the system via pyodbc for SQL and\nparamiko for SFTP transfers.
\n", "bases": "iLibrary.src.getInfoForLibrary.getInfoForLibrary, iLibrary.src.saveLibrary.saveLibrary"}, {"fullname": "iLibrary.src.Library.Library.__init__", "modulename": "iLibrary.src.Library", "qualname": "Library.__init__", "kind": "function", "doc": "Initializes the class attributes for a database connection.\nThe actual connection is established in the __enter__ method.
\n\nArgs:\n db_user (str): The user ID for the database connection.\n db_password (str): The password for the database user.\n db_host (str): The system/host name for the database connection.\n db_driver (str): The ODBC driver to be used.
\n", "signature": "(db_user: str, db_password: str, db_host: str, db_driver: str)"}, {"fullname": "iLibrary.src.Library.Library.db_user", "modulename": "iLibrary.src.Library", "qualname": "Library.db_user", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_host", "modulename": "iLibrary.src.Library", "qualname": "Library.db_host", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_driver", "modulename": "iLibrary.src.Library", "qualname": "Library.db_driver", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.db_password", "modulename": "iLibrary.src.Library", "qualname": "Library.db_password", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.Library.Library.iclose", "modulename": "iLibrary.src.Library", "qualname": "Library.iclose", "kind": "function", "doc": "A helper method to close the connection, also useful for manual closure.
\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.User", "modulename": "iLibrary.src.User", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.User.User", "modulename": "iLibrary.src.User", "qualname": "User", "kind": "class", "doc": "A class to manage User on IBMi System
\n\nIt provides methods to connect to the system via pyodbc for SQL and\nparamiko for SFTP transfers.
\n", "bases": "iLibrary.src.getUserInfoForUser.getUserInfoForUser, iLibrary.src.sendMSG.sendMSG"}, {"fullname": "iLibrary.src.User.User.__init__", "modulename": "iLibrary.src.User", "qualname": "User.__init__", "kind": "function", "doc": "Initializes the class attributes for a database connection.\nThe actual connection is established in the __enter__ method.
\n\nArgs:\n db_user (str): The user ID for the database connection.\n db_password (str): The password for the database user.\n db_host (str): The system/host name for the database connection.\n db_driver (str): The ODBC driver to be used.
\n", "signature": "(db_user: str, db_password: str, db_host: str, db_driver: str)"}, {"fullname": "iLibrary.src.User.User.db_user", "modulename": "iLibrary.src.User", "qualname": "User.db_user", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_host", "modulename": "iLibrary.src.User", "qualname": "User.db_host", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_driver", "modulename": "iLibrary.src.User", "qualname": "User.db_driver", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.db_password", "modulename": "iLibrary.src.User", "qualname": "User.db_password", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.User.User.iclose", "modulename": "iLibrary.src.User", "qualname": "User.iclose", "kind": "function", "doc": "A helper method to close the connection, also useful for manual closure.
\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary", "modulename": "iLibrary.src.getInfoForLibrary", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary", "kind": "class", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.__init__", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.__init__", "kind": "function", "doc": "\n", "signature": "(connection)"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.conn", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.conn", "kind": "variable", "doc": "\n"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getLibraryInfo", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getLibraryInfo", "kind": "function", "doc": "\n", "signature": "(self, library: str, wantJson=True):", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getFileInfo", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getFileInfo", "kind": "function", "doc": "\n", "signature": "(self, library: str, qFiles: bool = False) -> str:", "funcdef": "def"}, {"fullname": "iLibrary.src.getInfoForLibrary.getInfoForLibrary.getAllLibraries", "modulename": "iLibrary.src.getInfoForLibrary", "qualname": "getInfoForLibrary.getAllLibraries", "kind": "function", "doc": "\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "iLibrary.src.getUserInfoForUser", "modulename": "iLibrary.src.getUserInfoForUser", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser", "kind": "class", "doc": "Handles user information retrieval and messaging functionalities.
\n\nThis class provides methods to interact with the database for retrieving user information\nand to send messages to specified users. It supports data retrieval in different formats\n(e.g., JSON or tuple), and it enables system messaging with configurable options.
\n\n:ivar conn: Database connection object used for executing queries.
\n"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser.getAllUsers", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser.getAllUsers", "kind": "function", "doc": "Retrieves all user information from the database. Optionally returns the data in\nJSON format depending on the provided parameter.
\n\nRetrieves a list of users stored in the database and can output the data either as\na list of tuples or in JSON format. The query fetches all fields available in the\nuser information database table and handles cases where no data is found.
\n\n\n\n", "signature": "(self, wantJson: bool = False):", "funcdef": "def"}, {"fullname": "iLibrary.src.getUserInfoForUser.getUserInfoForUser.getSingleUserInformation", "modulename": "iLibrary.src.getUserInfoForUser", "qualname": "getUserInfoForUser.getSingleUserInformation", "kind": "function", "doc": "The data fetched from the database. When
\nwantJsonis True, returns a\n JSON object as a string. Otherwise, returns a list of tuples.
Retrieves information about a specific user from the database based on their username. The function supports\nreturning data either as a JSON-formatted string or as a tuple with corresponding database fields.
\n\n\n\n\nA tuple containing database fields if
\nwantJsonis False, or a JSON-formatted string ifwantJsonis True.\n If no user is found, returns either a JSON-formatted error message or a tuple with error details, based on the\n value ofwantJson. Returns None if an exception occurs.
username input is empty or None.Saves a library to a specified save file, providing options for further customization such\nas setting the target release, saving as a zip file, specifying the device, volume, and more.
\n\nlibrary if not specified.getZip is set\nto True. Must be an absolute path.getZip is set to True. Must be an absolute path.getZip is\nenabled.\n\n", "signature": "(\tself,\tlibrary: str,\tsaveFileName: str,\tdev: str = None,\tvol: str = None,\ttoLibrary: str = None,\tdescription: str = None,\tlocalPath: str = None,\tremPath: str = None,\tgetZip: bool = False,\tport: int = None,\tremSavf=True,\tversion: str = None,\tmax_records: int | str | None = None,\tasp: int | str | None = None,\twaitFile: int | str | None = None,\tshare: str = None,\tauthority: str = None) -> bool:", "funcdef": "def"}, {"fullname": "iLibrary.src.saveLibrary.saveLibrary.removeFile", "modulename": "iLibrary.src.saveLibrary", "qualname": "saveLibrary.removeFile", "kind": "function", "doc": "A boolean indicating whether the library was successfully saved. Returns True on\n success or False on failure.
\n
Removes a save file from the specified library.
\n\nThis function executes the system command to delete a save file from an IBM i\nsystem. It connects to the database through a cursor, and attempts to perform\nthe operation. If an error is encountered during execution, the function\nrolls back the transaction and logs the error. On success, the transaction\nis committed.
\n\n\n\n", "signature": "(self, library: str, saveFileName: str) -> bool:", "funcdef": "def"}, {"fullname": "iLibrary.src.sendMSG", "modulename": "iLibrary.src.sendMSG", "kind": "module", "doc": "\n"}, {"fullname": "iLibrary.src.sendMSG.sendMSG", "modulename": "iLibrary.src.sendMSG", "qualname": "sendMSG", "kind": "class", "doc": "True if the save file is removed successfully, otherwise False.
\n
Handles message-related operations by providing functionality to send messages\nto specific users within the system. The class interacts with system APIs to\nexecute the required operations and ensures the input parameters are validated\nbefore proceeding with the message sending process.
\n\nAttributes supported by this class are not specified because the class relies\non method-level operations.
\n"}, {"fullname": "iLibrary.src.sendMSG.sendMSG.send_message_to_user", "modulename": "iLibrary.src.sendMSG", "qualname": "sendMSG.send_message_to_user", "kind": "function", "doc": "Sends a message to a specified user on the system. This method interacts with the system\nto send a message by executing an SQL query. It validates the required\ninputs and raises an exception if they are missing. Optional parameters for\nfurther message configuration can also be provided.
\n\n\n\n\nNone if the message is sent successfully.
\n
ValueError: If any required parameter, such as username or message, is missing.
Exception: Any other exceptions that occur during the execution of the\nSQL query are raised, indicating issues during the process of sending the\nmessage.