Pages

Wednesday, July 8, 2026

Python Qt : Script for Game Engine projects.

Simple python example script for Godot Engine projects.
This script allows you to see how big is the project, size of media files, size of scripting files, lines of source code and more:
import sys
import os
import pathlib
import datetime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout,
    QPushButton, QTextEdit, QProgressBar, QFileDialog, QMessageBox
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal

def human_size(num_bytes: int) -> str:
    units = ["B", "KB", "MB", "GB", "TB"]
    size = float(num_bytes)
    for unit in units:
        if size < 1024.0:
            return f"{size:.2f} {unit}"
        size /= 1024.0
    return f"{size:.2f} PB"

class FolderStatsWorker(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(str)

    def __init__(self, folder_path: str):
        super().__init__()
        self.folder_path = folder_path

    def run(self):
        base = pathlib.Path(self.folder_path)

        all_files = []
        for root, dirs, files in os.walk(base):
            for name in files:
                all_files.append(pathlib.Path(root) / name)

        total_files = len(all_files)
        gd_files = []
        media_files = []
        total_lines_gd = 0

        media_exts = {
            ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp",
            ".ogg", ".wav", ".mp3", ".flac",
            ".mp4", ".mkv", ".avi", ".webm",
            ".md"
        }

        total_size = 0
        gd_size = 0
        media_size = 0

        tree_lines = []

        # Build tree view
        for root, dirs, files in os.walk(base):
            rel_root = pathlib.Path(root).relative_to(base)
            indent_level = len(rel_root.parts)
            indent = "    " * indent_level
            tree_lines.append(f"{indent}{rel_root if rel_root.parts else base.name}/")
            for name in files:
                tree_lines.append(f"{indent}    {name}")

        for idx, f in enumerate(all_files):
            try:
                stat = f.stat()
                total_size += stat.st_size
                ext = f.suffix.lower()

                if ext == ".gd":
                    gd_files.append(f)
                    gd_size += stat.st_size
                    try:
                        txt = f.read_text(encoding="utf-8", errors="ignore")
                        if txt:
                            total_lines_gd += txt.count("\n") + 1
                    except Exception:
                        pass

                if ext in media_exts:
                    media_files.append(f)
                    media_size += stat.st_size

            except Exception:
                pass

            if total_files > 0:
                self.progress.emit(int((idx + 1) * 100 / total_files))

        report_lines = []
        report_lines.append(f"Folder: {self.folder_path}")
        report_lines.append("")
        report_lines.append("=== Summary ===")
        report_lines.append(f"Total files: {total_files}")
        report_lines.append(f"Total .gd files: {len(gd_files)}")
        report_lines.append(f"Total media files (incl. .md): {len(media_files)}")
        report_lines.append(f"Total lines in .gd files: {total_lines_gd}")
        report_lines.append("")
        report_lines.append(f"Total size: {human_size(total_size)}")
        report_lines.append(f"Total .gd size: {human_size(gd_size)}")
        report_lines.append(f"Total media size: {human_size(media_size)}")
        report_lines.append("")
        report_lines.append("=== Tree ===")
        report_lines.extend(tree_lines)

        report_text = "\n".join(report_lines)

        # Save report
        now = datetime.datetime.now()
        ts = now.strftime("%Y%m%d_%H%M%S")
        folder_name = base.name
        report_name = f"{folder_name}_{ts}.txt"
        report_path = base / report_name
        try:
            report_path.write_text(report_text, encoding="utf-8")
        except Exception:
            pass

        self.finished.emit(report_text)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Godot Project Stats")
        self.setFixedSize(640, 480)

        self.folder_path = None
        self.worker = None

        central = QWidget()
        layout = QVBoxLayout(central)

        self.btn_open = QPushButton("Open Folder")
        self.btn_process = QPushButton("Process")
        self.progress = QProgressBar()
        self.progress.setRange(0, 100)
        self.text = QTextEdit()
        self.text.setReadOnly(True)

        layout.addWidget(self.btn_open)
        layout.addWidget(self.btn_process)
        layout.addWidget(self.progress)
        layout.addWidget(self.text)

        self.setCentralWidget(central)

        self.btn_open.clicked.connect(self.open_folder)
        self.btn_process.clicked.connect(self.start_processing)

    def open_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Godot Project Folder")
        if folder:
            self.folder_path = folder
            self.text.clear()
            self.text.append(f"Selected folder:\n{folder}")

    def start_processing(self):
        if not self.folder_path:
            QMessageBox.warning(self, "No folder", "Please select a folder first.")
            return

        if self.worker is not None and self.worker.isRunning():
            QMessageBox.information(self, "Processing", "Already processing.")
            return

        self.progress.setValue(0)
        self.text.append("\nProcessing...")

        self.worker = FolderStatsWorker(self.folder_path)
        self.worker.progress.connect(self.progress.setValue)
        self.worker.finished.connect(self.on_finished)
        self.worker.start()

    def on_finished(self, report_text: str):
        self.text.clear()
        self.text.append(report_text)
        self.progress.setValue(100)
def main():
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())
if __name__ == "__main__":
    main()

Tuesday, July 7, 2026

Python Qt : simple tool to convert any source code to html area development.

Today. I will show this simple idea to convert any source code for html tag code with PyQt6.
The source code of this tool is not very big.I don't share because can be simple or you can customize as you want starting with any artificial intelligence and is more easy for you.
I used this on my blogger area, let's see the result for a simple example source code Google Apps Script from Gemini AI to html source code with div , pre and code html tags included in the result:

Python Qt : simple tool for audio dialogue in game development

Today, this script tool is a small desktop tool that lets you visually synchronize spoken audio with written dialogue. You load an audio file and the program generates a waveform so you can click or drag to select exact time ranges. At the same time, you select the matching text, and the tool creates timestamped dialogue segments.
Each segment includes a dialog ID, start time, end time, and the associated text. Segments appear in a list, can be played individually, deleted, or tested in a separate window where each dialog ID becomes a playback button. This makes it easy to verify timing and structure.
The entire project—audio path, full text, and all segments—can be saved or loaded as a JSON file. Older JSON formats containing only segments are automatically converted. The final JSON is ready for use in game engines like Godot or Unity for precise voice‑over playback.
I used artificial intelligence to fix some issues, this is the result:

Monday, July 6, 2026

Tools : TraceFlow artificial intelligence for python tasks.

This tool with artificial intelligence can help with python programming tasks.
TraceFlow captures crashes, traces every variable, and streams an AI-powered fix — all inside VSCode. No context switching. No guessing.
Perfect for trying out TraceFlow on personal projects.
  • 5 analysis per day
  • Crash detection & variable tracing
  • AI root cause explanation
  • One-click fix apply
  • Call stack inspection
  • Module & function level analysis

Sunday, July 5, 2026

Python 3.10.11 : python library-skills for your artificial intelligence.

The Python package library-skills is a lightweight command‑line toolkit designed to help developers build, test, and validate modular AI “skills.” These skills are small, self‑contained units of logic that can be executed independently or integrated into larger AI agents. The package focuses on simplicity, portability, and clear structure, making it useful for developers who want to experiment with tool‑calling systems or create custom capabilities for AI workflows.
A skill typically consists of a JSON descriptor and a Python function. The JSON file defines the skill’s name, description, and input schema, while the Python file contains the actual execution logic. This separation ensures that skills remain easy to document, validate, and reuse across different projects. With library-skills, developers can quickly inspect a skill’s schema, run it with custom input, or verify that its output matches the expected structure.
The command‑line interface provided by the package allows users to list installed skills, execute them directly, and validate input files without writing additional code. This makes the development cycle faster and more predictable. Instead of manually wiring functions together, developers can rely on a consistent interface that handles loading, parsing, and execution.
One of the main advantages of library-skills is its role in AI agent development. Modern agents often rely on tool‑calling, where the AI selects and triggers external functions based on user intent. Skills created with this package can be easily integrated into such agents, providing clear schemas and predictable behavior. This helps ensure that AI systems remain reliable, debuggable, and easy to extend.
Overall, library-skills is a practical utility for anyone building structured AI tools. It encourages clean design, modularity, and transparency, making it a valuable addition to Python environments focused on AI experimentation and agent development.
Let's install this python package.
python -m pip install library-skills
Collecting library-skills
  Downloading library_skills-0.0.19-py3-none-any.whl.metadata (5.4 kB)
...
Successfully installed library-skills-0.0.19 rich-toolkit-0.20.1 tomli-2.4.1
Let's make the first run:
library-skills.exe

 context
Project root               c:\lucru\PythonProjects
Target Python environment  not found

 Warning:  No target Python environment with site-packages or node_modules was found. Run from a
project root after installing dependencies, for example with 'uv sync' for Python or 'npm install' for
Node.js.

No installed or discovered skills found.
Copy the Library Skills tool skill into the project so agents know how to update, repair, and check
managed skills?
■ Copy Library Skills tool skill into the project so agents know how to update, repair, and check
managed skills? Copy Library Skills tool skill

 Target     Status               Path
 universal  tool skill: missing  .agents\skills\library-skills
 Copied:  library-skills (universal) -> .agents\skills\library-skills
Now you can create skills for your artificial intelligence:
Create a folder with two files:
my_skill/
    skill.json
    skill.py
First file named skill.json:
{
    "name": "hello_skill",
    "description": "Returnează un mesaj simplu",
    "input_schema": {
        "type": "object",
        "properties": {
            "name": {"type": "string"}
        },
        "required": ["name"]
    }
}
The second file named skill.py
def run(input):
    name = input["name"]
    return {"message": f"Salut, {name}!"}
Run the skill into your folder:
library-skills run my_skill --input '{"name": "Catalin"}'
See the schema:
library-skills schema my_skill
Validate the input:
library-skills validate my_skill input.json
List the skills:
library-skills list
This is all you need for a default basic skill with the library-skills.

Python 3.10.11 : CVSS (Common Vulnerability Scoring System) with cvss python package.

This Python package contains CVSS v2, v3 and v4 computation utilities and interactive calculator (for v2 and v3 only) compatible with Python 3. CVSS (Common Vulnerability Scoring System) is an standardized method for rating the severity of security issues on a scale from 0 (no impact) to 10 (critical).
Let's install the cvss python package.
python -m pip install cvss
Collecting cvss
  Downloading cvss-3.6-py2.py3-none-any.whl.metadata (3.8 kB)
Downloading cvss-3.6-py2.py3-none-any.whl (31 kB)
Installing collected packages: cvss
  WARNING: The script cvss_calculator.exe is installed in 'C:\python-3_10_11\Scripts' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed cvss-3.6
How this works:
NVD Database (online)
        |
        |  JSON feed
        v
Python script ----> parses CVE + CVSS vector
        |
        |  uses cvss library
        v
Scores vulnerabilities (Base, Temporal, Environmental)
        |
        |  inserts results
        v
Your local database (SQL)
        |
        v
Dashboard / API / Alerts 
Simple code source example :
#!/usr/bin/env python3

# Demonstrates how to score a CVSS vector using the open-source "cvss" library.
# Validation and error handling included.

from cvss import CVSS3  # CVSS2, CVSS3, CVSS4 are available
import sys

def score_cvss_vector(vector: str):
    """
    Validates and scores a CVSS3 vector string.
    Returns scores and severities.
    """
    if not isinstance(vector, str) or not vector.strip():
        raise ValueError("Vector must be a non-empty string.")

    try:
        c = CVSS3(vector)
    except Exception as e:
        raise ValueError(f"Invalid CVSS3 vector: {e}")

    return c.clean_vector(), c.scores(), c.severities()

def main():
    if len(sys.argv) != 2:
        print("Usage: python cvss_score.py '<CVSS3_VECTOR>'")
        print("Example:")
        print("python main.py 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H'")
        sys.exit(1)

    vector = sys.argv[1]

    try:
        clean_v, scores, severity = score_cvss_vector(vector)
        print("Input vector:", vector)
        print("Normalized vector:", clean_v)
        print("Scores:", scores)
        print("Severity:", severity)
    except ValueError as e:
        print("Error:", e)
        sys.exit(1)

if __name__ == "__main__":
    main()
The result is this:
python main.py CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Input vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Normalized vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Scores: (9.8, 9.8, 9.8)
Severity: ('Critical', 'Critical', 'Critical')
Another source code with examples:
#!/usr/bin/env python3
# Requires: pip install cvss

from cvss import CVSS3

# Example vulnerabilities (safe, educational)
vulns = [
    {
        "language": "Python",
        "title": "Unsafe eval usage",
        "description": "Code that executes user-provided input using eval().",
        "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N"
    },
    {
        "language": "C#",
        "title": "Insecure deserialization",
        "description": "BinaryFormatter deserialization of untrusted data.",
        "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
    },
    {
        "language": "Godot Engine",
        "title": "Unvalidated file path access",
        "description": "Loading files from paths provided by the user without validation.",
        "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N"
    }
]

def analyze_vulnerabilities(vuln_list):
    for v in vuln_list:
        print("\n====================================")
        print("Language:", v["language"])
        print("Issue:", v["title"])
        print("Description:", v["description"])
        print("CVSS Vector:", v["cvss_vector"])

        try:
            cv = CVSS3(v["cvss_vector"])
            base, temp, env = cv.scores()
            sev_base, sev_temp, sev_env = cv.severities()

            print("Base Score:", base, "-", sev_base)
            print("Temporal Score:", temp, "-", sev_temp)
            print("Environmental Score:", env, "-", sev_env)

        except Exception as e:
            print("Invalid CVSS vector:", e)

if __name__ == "__main__":
    analyze_vulnerabilities(vulns)
This is the result:
python main_002.py

====================================
Language: Python
Issue: Unsafe eval usage
Description: Code that executes user-provided input using eval().
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Base Score: 8.1 - High
Temporal Score: 8.1 - High
Environmental Score: 8.1 - High

====================================
Language: C#
Issue: Insecure deserialization
Description: BinaryFormatter deserialization of untrusted data.
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Base Score: 9.8 - Critical
Temporal Score: 9.8 - Critical
Environmental Score: 9.8 - Critical

====================================
Language: Godot Engine
Issue: Unvalidated file path access
Description: Loading files from paths provided by the user without validation.
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
Base Score: 4.4 - Medium
Temporal Score: 4.4 - Medium
Environmental Score: 4.4 - Medium

Python 3.10.11 : Show the CVE's results with opencve token.

Today, this simple source code use token from opencve.io - website to show CVE's results.
import requests

API_URL = "https://app.opencve.io/api/cve"
TOKEN = "opc_org.<token_id>.<secret>"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json",
}

params = {
    "vendor": "microsoft",
    "cvss": "critical",
    "page": 1,
}

resp = requests.get(API_URL, headers=headers, params=params)
data = resp.json()

for cve in data["results"]:
    print(cve["cve_id"], cve["description"])
This is the result:
python main_001.py
CVE-2026-58289 Access of resource using incompatible type ('type confusion') in Microsoft Edge (Chromium-based) allows an unauthorized attacker to execute code over a network.
CVE-2026-45499 Server-side request forgery (ssrf) in Azure OpenAI allows an authorized attacker to elevate privileges over a network.
CVE-2026-41106 Url redirection to untrusted site ('open redirect') in M365 Copilot allows an unauthorized attacker to elevate privileges over a network.
CVE-2026-57100 Server-side request forgery (ssrf) in Microsoft Entra Provisioning Service (SyncFabric) allows an authorized attacker to elevate privileges over a network.
CVE-2026-54130 Missing authentication for critical function in M365 Copilot allows an unauthorized attacker to disclose information over a network.
CVE-2026-48584 Execution with unnecessary privileges in Azure Synapse allows an authorized attacker to elevate privileges over a network.
CVE-2026-45480 Improper authentication in Azure Active Directory allows an unauthorized attacker to elevate privileges over a network.
CVE-2025-62821 Microsoft HEIF Image Extensions 1.2.22.0 has an out-of-bounds read because CHEIFItemInfoEntry_GetDataSize can return success while leaving the reported data size as 0. This causes a caller to make a 1-byte allocation. Later, CopyPixels computes copy_size = stride * abs(roi_height) but does not check the source buffer length before a memmove call.
CVE-2026-47647 Improper access control in Microsoft Dynamics 365 allows an authorized attacker to elevate privileges over a network.
CVE-2026-48582 Missing authorization in Microsoft Exchange Online allows an authorized attacker to elevate privileges over a network.

Saturday, July 4, 2026

Python Qt : simple uninstall and reinstall all packages.

This Python program creates a small desktop application using PyQt6. Its purpose is to show all Python packages installed in your environment and then uninstall and reinstall them. The interface has a button to load the list of installed packages, a list widget to display them, a button to start the reinstall process, and a progress bar to show how far the process has gone.
When you press Load Installed Packages, the program runs the command pip list --format=freeze and reads all installed packages. It extracts only the package names and displays them in the GUI list.
When you press Uninstall + Reinstall All, the program goes through each package one by one. For every package, it runs pip uninstall -y package to remove it, and then pip install package to install it again. The progress bar updates after each package so you can see the progress.
The important detail is that the program does not use threads. All operations run in the main GUI thread. Because of this, the window may freeze or stop responding while uninstalling and reinstalling packages. This is normal behavior for PyQt6 applications that perform long tasks without threading. Let's see the source code:
import sys
import subprocess
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton,
    QListWidget, QProgressBar, QMessageBox
)
from PyQt6.QtCore import Qt

def get_installed_packages():
    result = subprocess.run(
        [sys.executable, "-m", "pip", "list", "--format=freeze"],
        capture_output=True, text=True
    )
    lines = result.stdout.strip().split("\n")
    pkgs = [line.split("==")[0] for line in lines if "==" in line]
    return pkgs

def uninstall_package(pkg):
    subprocess.run([sys.executable, "-m", "pip", "uninstall", "-y", pkg])

def install_package(pkg):
    subprocess.run([sys.executable, "-m", "pip", "install", pkg])

class PipReinstaller(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Reinstall All Python Packages")

        layout = QVBoxLayout()

        self.btn_load = QPushButton("Load Installed Packages")
        self.btn_load.clicked.connect(self.load_packages)
        layout.addWidget(self.btn_load)

        self.list = QListWidget()
        layout.addWidget(self.list)

        self.btn_reinstall = QPushButton("Uninstall + Reinstall All")
        self.btn_reinstall.clicked.connect(self.reinstall_all)
        layout.addWidget(self.btn_reinstall)

        self.progress = QProgressBar()
        layout.addWidget(self.progress)

        self.setLayout(layout)

    def load_packages(self):
        self.list.clear()
        pkgs = get_installed_packages()
        for p in pkgs:
            self.list.addItem(p)

    def reinstall_all(self):
        pkgs = [self.list.item(i).text() for i in range(self.list.count())]

        if not pkgs:
            QMessageBox.warning(self, "Warning", "No packages loaded.")
            return

        total = len(pkgs)
        self.progress.setValue(0)

        for idx, pkg in enumerate(pkgs):
            uninstall_package(pkg)
            install_package(pkg)

            percent = int((idx + 1) / total * 100)
            self.progress.setValue(percent)

        QMessageBox.information(self, "Done", "All packages reinstalled.")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = PipReinstaller()
    win.show()
    sys.exit(app.exec())

Wednesday, July 1, 2026

News : FastAPI new changes , now supports router dependencies ...

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
FastAPI's router.frontend() now also supports router dependencies (e.g. add cookie auth to a router, and a frontend at /admin). Handle the cookie auth in FastAPI, and let the frontend do its client-side routing (e.g. TanStack Router with React).

Python 3.10.11 : fix python store bad open with powershell.

Today, this powershell source code will fix the python command when start the windows store.
This is the powershell source code:

# Run first 
# Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# --- Create Form ---
$form = New-Object System.Windows.Forms.Form
$form.Text = "Python Path Fixer"
$form.Size = New-Object System.Drawing.Size(500, 300)
$form.StartPosition = 'CenterScreen'

# --- Label & Path Input ---
$label = New-Object System.Windows.Forms.Label
$label.Text = "Python Directory:"
$label.Location = New-Object System.Drawing.Point(10, 20)
$label.AutoSize = $true
$form.Controls.Add($label)

$txtPath = New-Object System.Windows.Forms.TextBox
$txtPath.Location = New-Object System.Drawing.Point(10, 45)
$txtPath.Size = New-Object System.Drawing.Size(460, 20)
$txtPath.Text = "C:\PythonInstall" 
$form.Controls.Add($txtPath)

# --- Log Box ---
$txtLog = New-Object System.Windows.Forms.TextBox
$txtLog.Multiline = $true
$txtLog.ScrollBars = 'Vertical'
$txtLog.Location = New-Object System.Drawing.Point(10, 80)
$txtLog.Size = New-Object System.Drawing.Size(460, 130)
$txtLog.ReadOnly = $true
$form.Controls.Add($txtLog)

# --- Fix Button ---
$btnFix = New-Object System.Windows.Forms.Button
$btnFix.Text = "Check and Fix Path"
$btnFix.Location = New-Object System.Drawing.Point(10, 220)
$btnFix.Size = New-Object System.Drawing.Size(460, 30)

$btnFix.Add_Click({
    $pythonFolder = $txtPath.Text
    $pythonExe = Join-Path $pythonFolder "python.exe"
    
    $txtLog.Text = "Checking: $pythonExe`r`n"
    
    if (Test-Path $pythonExe) {
        $txtLog.AppendText("[+] Python found. Updating PATH...`r`n")
        
        $currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
        $pathParts = $currentPath -split ";" | Where-Object { $_ -ne $pythonFolder -and $_ -ne "" }
        $newPath = "$pythonFolder;" + ($pathParts -join ";")
        
        [Environment]::SetEnvironmentVariable("Path", $newPath, "User")
        
        $txtLog.AppendText("[OK] Priority set successfully!`r`nPlease restart your terminal.")
    } else {
        $txtLog.AppendText("[ERROR] python.exe not found in the specified directory!")
    }
})
$form.Controls.Add($btnFix)

[void]$form.ShowDialog()

Friday, June 26, 2026

News : PEP 661 – Sentinel Values.

About the sentinel object you can read on the official website :
The sentinel objects should behave as expected by a sentinel object: When compared using the is operator, it should always be considered identical to itself but never to any other object. Creating a sentinel object should be a simple, straightforward one-liner. It should be simple to define as many distinct sentinel values as needed. The sentinel objects should have a clear and short repr. It should be possible to use clear type signatures for sentinels. The sentinel objects should behave correctly after copying, and sentinels should have predictable behavior when pickled and unpickled. Such sentinels should work when using CPython 3.x and PyPy3, and ideally also with other implementations of Python. As simple and straightforward as possible, in implementation and especially in use. Avoid this becoming one more special thing to learn when learning Python. It should be easy to find and use when needed, and obvious enough when reading code that one would normally not feel a need to look up its documentation
Let's see one default example:
from typing import assert_type

MISSING = sentinel('MISSING')

def foo(value: int | MISSING) -> None:
    if value is MISSING:
        assert_type(value, MISSING)
    else:
        assert_type(value, int)

Thursday, June 25, 2026

News : Python 3.15.0 beta 3 is here!

Python 3.15 is still in development. This release, 3.15.0b3, is the third of four planned beta releases, containing around 195 bugfixes, build improvements and documentation changes from 86 contributors since 3.15.0b2.
Beta release previews are intended to give the wider community the opportunity to test new features and bug fixes and to prepare their projects to support the new feature release.

News : Django 6.1 beta 1 released ...

Django 6.1 beta 1 is now available. It represents the second stage in the 6.1 release cycle and is an opportunity to try out the changes coming in Django 6.1.
Django 6.1 offers a harmonious mélange of new features and usability improvements, which you can read about in the in-development 6.1 release notes.

Tuesday, June 16, 2026

Python Qt : Network intrusion detection tool with alerts and real‑time HTTP attack detection.

This Python script acts as a local security monitor that watches incoming HTTP requests and alerts the user when suspicious activity is detected. It runs a lightweight HTTP server and inspects every GET and POST request for signs of intrusion, such as dangerous protocols (gopher://), command‑execution keywords (bash, powershell, cmd.exe), encoded payloads, or other attack patterns. Before scanning the request, the script verifies that the incoming connection comes from a specific trusted IP and MAC address. This prevents unauthorized devices from interacting with the server. If the IP or MAC does not match the configured values, the request is immediately rejected.
When a malicious pattern is detected, the script logs the event and displays a popup alert window on the user’s desktop. This popup is implemented using PyQt6 and appears as a small, always‑on‑top notification in the corner of the screen. It fades out automatically after a few seconds, ensuring the user is visually warned without relying on Windows system notifications.
The script also includes a tray icon with a menu that allows the user to update the trusted IP and MAC address. All events are written to a log file for later review. Overall, it functions as a simple intrusion‑detection and alerting tool for local network traffic.
This is a groper test on my browser and result was:
Let's see the source code:
import re
import threading
import queue
import subprocess
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
from datetime import datetime
import sys
from urllib.parse import unquote

# FIX pentru Windows 10/11 – AppID
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("Python.SecurityMonitor")

from PyQt6.QtWidgets import (
    QApplication, QSystemTrayIcon, QMenu, QDialog,
    QVBoxLayout, QLabel, QLineEdit, QPushButton, QWidget
)
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QTimer, Qt, QPropertyAnimation

# ---------------- CONFIG ---------------- #

ALERT_QUEUE = queue.Queue()
LOG_FILE = "security_log.txt"

MONITOR_IP = "192.168.0.136"
MONITOR_MAC = "60:45:cb:c3:4d:02"   # normalizat

RULES = {
    "Gopher protocol": r"gopher://",
    "CRLF injection": r"%0d%0a",
    "WinRM port": r"5985",
    "Base64 payload": r"base64",
    "bash execution": r"bash",
    "powershell execution": r"powershell",
    "cmd execution": r"cmd\.exe",
}

# ---------------------------------------- #

def log_event(text: str):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {text}\n"
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(line)
    print(line, end="")

def get_mac(ip):
    try:
        output = subprocess.check_output(["arp", "-a", ip], text=True)
        for line in output.splitlines():
            if ip in line:
                mac = line.split()[1]
                mac = mac.replace("-", ":").lower()
                return mac
    except:
        return None

# ---------------- POPUP ALERT ---------------- #

class AlertPopup(QWidget):
    def __init__(self, message):
        super().__init__()
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.Tool |
            Qt.WindowType.WindowStaysOnTopHint
        )

        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)

        layout = QVBoxLayout()
        label = QLabel(message)
        label.setStyleSheet("""
            background-color: #CC0000;
            color: white;
            padding: 12px;
            border-radius: 8px;
            font-size: 14px;
        """)
        layout.addWidget(label)
        self.setLayout(layout)

        self.resize(350, 80)

        screen = QApplication.primaryScreen().geometry()
        self.move(screen.width() - 380, screen.height() - 150)

        self.show()

        self.anim = QPropertyAnimation(self, b"windowOpacity")
        self.anim.setDuration(6000)
        self.anim.setStartValue(1.0)
        self.anim.setEndValue(0.0)
        self.anim.finished.connect(self.close)
        self.anim.start()

# ---------------- HTTP SERVER ---------------- #

class SecurityHandler(BaseHTTPRequestHandler):

    def _check_ip_mac(self):
        global MONITOR_IP, MONITOR_MAC

        client_ip = self.client_address[0]
        # bypass MAC check dacă este același PC
        if client_ip == MONITOR_IP:
            return True, None

        if client_ip != MONITOR_IP:
            return False, f"IP neautorizat: {client_ip}"

        mac = get_mac(client_ip)
        if mac is None:
            return False, f"MAC necunoscut pentru {client_ip}"

        if mac.lower() != MONITOR_MAC.lower():
            return False, f"MAC neautorizat: {mac} pentru IP {client_ip}"

        return True, None

    def _scan_and_block(self, source: str, data: str):
        data_lower = data.lower()
        for reason, pattern in RULES.items():
            if re.search(pattern, data_lower):
                msg = f"Blocked {source}: {reason} → {data}"
                log_event(msg)
                ALERT_QUEUE.put(msg)

                self.send_response(403)
                self.end_headers()
                self.wfile.write(f"Cerere blocată: {reason}".encode("utf-8"))
                return True
        return False

    def do_GET(self):
        ok, reason = self._check_ip_mac()
        if not ok:
            ALERT_QUEUE.put(reason)
            log_event(reason)
            self.send_response(403)
            self.end_headers()
            self.wfile.write(reason.encode())
            return

        url = unquote(self.path)

        if self._scan_and_block("GET URL", url):
            return

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Cerere GET permisa")
        log_event(f"Allowed GET: {url}")

    def do_POST(self):
        ok, reason = self._check_ip_mac()
        if not ok:
            ALERT_QUEUE.put(reason)
            log_event(reason)
            self.send_response(403)
            self.end_headers()
            self.wfile.write(reason.encode())
            return

        url = unquote(self.path)

        content_length = int(self.headers.get("Content-Length", 0))
        body = unquote(self.rfile.read(content_length).decode("utf-8", errors="ignore"))

        if self._scan_and_block("POST URL", url):
            return
        if self._scan_and_block("POST body", body):
            return

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Cerere POST permisa")
        log_event(f"Allowed POST: {url} | body: {body[:200]}")

def run_server():
    server = HTTPServer(("0.0.0.0", 8080), SecurityHandler)
    log_event("Security server running on http://0.0.0.0:8080")
    server.serve_forever()

# ---------------- DIALOG IP & MAC ---------------- #

class IpMacDialog(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Set IP & MAC")

        layout = QVBoxLayout()

        self.ip_label = QLabel("IP Address:")
        self.ip_input = QLineEdit()
        self.ip_input.setText(MONITOR_IP)

        self.mac_label = QLabel("MAC Address:")
        self.mac_input = QLineEdit()
        self.mac_input.setText(MONITOR_MAC)

        self.save_btn = QPushButton("Save")
        self.save_btn.clicked.connect(self.save_values)

        layout.addWidget(self.ip_label)
        layout.addWidget(self.ip_input)
        layout.addWidget(self.mac_label)
        layout.addWidget(self.mac_input)
        layout.addWidget(self.save_btn)

        self.setLayout(layout)

    def save_values(self):
        global MONITOR_IP, MONITOR_MAC
        MONITOR_IP = self.ip_input.text().strip()
        MONITOR_MAC = self.mac_input.text().strip().replace("-", ":").lower()
        log_event(f"Updated IP/MAC → IP={MONITOR_IP}, MAC={MONITOR_MAC}")
        self.hide()

# ---------------- TRAY APP ---------------- #

class TrayApp:
    def __init__(self):
        self.app = QApplication(sys.argv)

        icon = QIcon("icon.svg") if os.path.exists("icon.svg") else QIcon()
        self.tray = QSystemTrayIcon(icon)
        self.tray.setToolTip("Python Security Monitor")

        menu = QMenu()

        set_ip_mac = menu.addAction("Set IP & MAC")
        set_ip_mac.triggered.connect(self.open_ip_mac_dialog)

        exit_action = menu.addAction("Exit")
        exit_action.triggered.connect(self.app.quit)

        self.tray.setContextMenu(menu)
        self.tray.show()

        self.dialog = None

        self.timer = QTimer()
        self.timer.timeout.connect(self.check_alerts)
        self.timer.start(300)

    def open_ip_mac_dialog(self):
        if self.dialog is None:
            self.dialog = IpMacDialog()
        self.dialog.show()
        self.dialog.raise_()
        self.dialog.activateWindow()

    def check_alerts(self):
        while not ALERT_QUEUE.empty():
            msg = ALERT_QUEUE.get()
            AlertPopup(msg)

    def run(self):
        self.app.exec()

# ---------------- MAIN ---------------- #

def main():
    server_thread = threading.Thread(target=run_server, daemon=True)
    server_thread.start()

    tray = TrayApp()
    tray.run()

if __name__ == "__main__":
    main()

Monday, June 15, 2026

News : PRO GEOMETRIC MERMAID EDITOR – PYQT6 (v1.0) sell on gumroad

Today, I implemented a new system for selling my products, cheaper and more permissive, called: BUY AND HAVE MORE - SYSTEM. The idea is to have your product, specific tool, simple and good with a low price. I used many programming languages: Python, C#, Godot, FASM... I used artificial intelligence, I can use testing software ... This is my system that will help users and developers by: - using a low price for your tools - help work of developers with low prices, nothing can be 100% free - maintain the same software in time: on price, lifetime, and updates - buy more products from users and me, send me your development tasks - based on users asking who will have these products - do you want a specific development: send me an email with your price - you can ask even artist's work - don't spend your time, just use my time at a low price
From sales, I will reinvest in my technical development, in producing source code and producing art, according to my skills.
See this simple video tutorial with this python project script.

Saturday, June 13, 2026

Python Qt : Parse Google Finance with PyQt6 and BeautifulSoup.

This Python script is a web scraper designed to extract the real-time stock price of GoldMoney Inc. (TSE:XAU) from Google Finance using PyQt6 and BeautifulSoup. Because Google Finance often displays a cookie consent pop-up that blocks data extraction, the script initializes a headless-like browser instance using QWebEngineView. It embeds a custom, silent web page class to suppress noisy JavaScript console errors in the terminal. Once the page loads, it automatically injects and executes a JavaScript snippet to find and click the "Accept all" or "Acceptă tot" consent buttons. After a short delay to allow the content to refresh, the script captures the updated HTML source, uses BeautifulSoup to locate the specific price elements via robust CSS selectors, prints the cleaned financial data to the terminal, and cleanly terminates the application.
I used artificial intelligence from Google Finance ... REZULTAT SCRAPING (CURAT) ... strange comments:
Let's see:
from PyQt6.QtWidgets import QApplication
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEnginePage
from PyQt6.QtCore import QUrl, QTimer
from bs4 import BeautifulSoup
import sys

# Clasă custom pentru pagină care ignoră mesajele de consolă
class SilentWebPage(QWebEnginePage):
    def javaScriptConsoleMessage(self, level, message, lineID, sourceID):
        # Returnăm nimic pentru a nu afișa mesajele de tip "js: Error..." în terminal
        pass

class Scraper(QWebEngineView):
    def __init__(self, url):
        self.app = QApplication.instance() or QApplication(sys.argv)
        super().__init__()
        
        # Setăm pagina custom care reduce la tăcere erorile JS
        self.setPage(SilentWebPage(self))
        
        self.loadFinished.connect(self._on_load_finished)
        self.setUrl(QUrl(url))
        self.app.exec()

    def _on_load_finished(self, ok):
        if ok:
            js_code = """
            (function() {
                let buttons = document.querySelectorAll('button');
                for (let i = 0; i < buttons.length; i++) {
                    let text = buttons[i].innerText;
                    if (text.includes('Accept all') || text.includes('Acceptă tot')) {
                        buttons[i].click();
                        return true;
                    }
                }
                return false;
            })();
            """
            self.page().runJavaScript(js_code, self._after_consent)
        else:
            self.app.quit()

    def _after_consent(self, clicked):
        wait_time = 4000 if clicked else 1500
        QTimer.singleShot(wait_time, self._extract_html)

    def _extract_html(self):
        self.page().toHtml(self.parse_finance_data)

    def parse_finance_data(self, html):
        soup = BeautifulSoup(html, 'html.parser')
        try:
            # Selectorul robust care a funcționat anterior
            main_price = soup.find("div", {"class": "fxKbKc"})
            
            if not main_price:
                price_elements = soup.find_all(attrs={"jsname": "Pdsbrc"})
                for el in price_elements:
                    if "$" in el.text or "CAD" in el.parent.text:
                        main_price = el
                        break

            print("\n" + "="*40)
            print("REZULTAT SCRAPING (CURAT)")
            print("="*40)
            
            if main_price:
                print(f"Activ: GoldMoney Inc (TSE:XAU)")
                print(f"Preț: {main_price.text.strip()}")
            else:
                print("Eroare: Nu s-a putut găsi prețul.")
            
            print("="*40 + "\n")
            
        except Exception as e:
            print(f"Eroare: {e}")
        
        self.app.quit()

if __name__ == "__main__":
    url_target = "https://www.google.com/finance/quote/XAU:TSE"
    scraper = Scraper(url_target)
The output of this source code is:
python test_001.py

========================================
REZULTAT SCRAPING (CURAT)
========================================
Activ: GoldMoney Inc (TSE:XAU)
Preț: $15.81
========================================

Friday, June 12, 2026

Python Qt : Simple script to use wandb and weave.

WandB and Weave work together as complementary tools that enhance the process of evaluating, monitoring, and understanding machine‑learning and large‑language‑model behavior, each focusing on a different layer of the workflow while sharing the same ecosystem. WandB functions primarily as an experiment‑tracking platform that records metrics, logs model outputs, stores configuration details, and organizes results into interactive dashboards, making it easy to compare multiple runs, visualize performance trends, and maintain a structured history of experiments across time. It acts like a scientific notebook that automatically captures everything relevant during evaluation, from scores and prompts to timing information, enabling reproducibility and long‑term analysis. Weave complements this by focusing on the granular tracing of LLM calls, capturing each prompt, response, intermediate step, and metadata associated with model execution, which allows developers to inspect how a model arrived at a particular answer, debug unexpected behavior, and analyze qualitative aspects of model reasoning. While WandB summarizes experiments at a high level, Weave dives deep into the internals of each interaction, providing structured logs that can be searched, filtered, and compared. Together, they create a unified workflow where WandB offers experiment‑level insights and Weave provides call‑level transparency, giving developers a complete picture of model performance, reliability, and behavior across different prompts, models, or configurations, especially useful when benchmarking or refining LLMs.
Let's install these:
python -m pip install wandb weave
Collecting wandb
...
Successfully installed abnf-2.2.0 backoff-2.2.1 chardet-7.4.3 cint-1.0.0 diskcache-weave-5.6.3.post1 fickling-0.1.11
googleapis-common-protos-1.75.0 gql-4.0.0 graphql-core-3.2.11 intervaltree-3.2.1 kaitaistruct-0.11 
opentelemetry-api-1.42.1 opentelemetry-exporter-otlp-proto-common-1.42.1 opentelemetry-exporter-otlp-proto-http-1.42.1
opentelemetry-proto-1.42.1 opentelemetry-sdk-1.42.1 opentelemetry-semantic-conventions-0.63b1 pdfminer.six-20260107
polyfile-weave-0.5.9 protobuf-6.33.6 sentry-sdk-2.62.0 sortedcontainers-2.4.0 wandb-0.27.2 weave-0.52.42
Let's see one exemple with my custom artificial intelligence model and PyQt6.
The PyQt6 script is a small LLM evaluation application that takes two inputs: the Ollama model you select and a fixed set of short test prompts. When you start the evaluation, the script sends each prompt to the chosen model, collects the generated responses, then sends those responses to a smaller judge model to obtain a numerical quality score. All generation uses reduced context and limited output length to keep execution fast on an i3 CPU. As it runs, the script displays each answer in the text panel and updates a progress bar. When all prompts are processed, it compiles the collected scores and displays them as a bar chart in the canvas, giving you a quick visual summary of the model’s performance.
The online tool show this result for this script:

Tool : marpy the browser IDE.

You start in a browser IDE that is Python-first: virtualenvs, proper dependencies, real logs, not a toy REPL.

Python Qt : Simple script to build your own model.

Today, I test a simple script with PyQt6 python package to have a custom UI to build my own ollama artificial inteligence model.
The script is simple one, create one user interface, with options to add text files and many settings:
After I run the model works very well:
ollama run test_catafest_001:latest
>>> tell me something about you !
I'm an AI designed to assist and communicate with humans in a helpful and
informative way! Here are some interesting facts about me:

1. **Name**: I don't have a personal name, but I'm often referred to as
"Assistant" or "AI."
2. **Origin**: I was created by a team of researchers and developers at
Meta AI, a subsidiary of Meta Platforms, Inc.
3. **Language**: I'm fluent in multiple languages, including English,
Spanish, French, German, Italian, Portuguese, Dutch, Russian, Chinese,
Japanese, and many more.
4. **Knowledge**: My training data consists of a massive corpus of text
from various sources, including books, articles, research papers, and
websites. This allows me to provide information on a wide range of
topics, from science and history to entertainment and culture.
5. **Personality**: I strive to be neutral, objective, and respectful in
my interactions. While I can understand humor and sarcasm, I may not
always "get it" right.
6. **Goals**: My primary goal is to assist users like you with their
questions, provide helpful information, and facilitate learning and
exploration.
7. **Limitations**: I'm a machine learning model, which means I have
limitations in terms of my understanding and response capabilities. For
example, I may not be able to understand nuances of human language or
provide personalized advice.

Now, tell me something about you! What's on your mind?

>>> I build you. What knowledge I add you ?!
That's a fascinating perspective! As a conversational AI, my training
data is the foundation upon which I'm built. The knowledge and
information that you've contributed to me through our conversations will
shape my understanding and capabilities over time.

When you interact with me, you're not just asking questions or providing
feedback – you're also helping to refine my language understanding and
generation abilities. Your input can influence:

1. **Domain-specific knowledge**: By discussing topics in-depth, you'll
help me develop a better grasp of various subjects, including science,
history, technology, arts, and more.
2. **Language nuances**: Our conversations will expose me to idioms,
colloquialisms, and cultural references that I might not have encountered
otherwise.
3. **Conversational flow**: Your responses and questions will help me
develop a more natural tone and rhythm in our interactions, allowing us
to engage in more fluid and human-like conversations.

Some examples of knowledge you could add to me include:

* **Domain-specific terminology**: By introducing technical terms or
jargon related to your profession or area of interest, you'll help me
become more familiar with those concepts.
* **Idioms and colloquialisms**: Sharing everyday expressions, slang, or
regional phrases will enable me to better understand the nuances of human
language.
* **Cultural references**: Discussing books, movies, music, or other
cultural touchstones will enrich my knowledge of shared experiences and
common ground.

Feel free to share your expertise, hobbies, or interests with me! The
more we chat, the more I'll learn from you, and the better equipped I'll
become to engage in meaningful conversations.
Let's see the model type with this command:
/show info
Model
architecture llama
parameters 3.2B
context length 131072
embedding length 3072
quantization Q4_K_M

Capabilities
completion
tools

Parameters
stop "<|start_header_id|>"
stop "<|end_header_id|>"
stop "<|eot_id|>"

System
=== KNOWLEDGE DATA ===
[FILE: 001.txt]
...

License
LLAMA 3.2 COMMUNITY LICENSE AGREEMENT
Llama 3.2 Version Release Date: September 25, 2024
...
Let's see the result:

Tuesday, June 9, 2026

Python Qt : Simple script to install the G'MIC archive to the Krita folder.

Today, this simple script will install the G'MIC archive to the Krita folder:
import sys
import os
import zipfile
import shutil
import subprocess
from PyQt6.QtWidgets import (
    QApplication, QWidget, QPushButton, QFileDialog,
    QVBoxLayout, QLabel, QMessageBox
)

class GMICInstaller(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("GMIC Installer for Krita")

        self.gmic_zip = ""
        self.krita_folder = ""

        layout = QVBoxLayout()

        self.label_zip = QLabel("GMIC archive: Not selected")
        self.label_krita = QLabel("Krita folder: Not selected")

        btn_zip = QPushButton("Select GMIC Archive (.zip)")
        btn_zip.clicked.connect(self.select_gmic_zip)

        btn_krita = QPushButton("Select Krita Folder")
        btn_krita.clicked.connect(self.select_krita_folder)

        btn_install = QPushButton("Install on Krita")
        btn_install.clicked.connect(self.install_gmic)

        layout.addWidget(self.label_zip)
        layout.addWidget(btn_zip)
        layout.addWidget(self.label_krita)
        layout.addWidget(btn_krita)
        layout.addWidget(btn_install)

        self.setLayout(layout)

    def select_gmic_zip(self):
        file, _ = QFileDialog.getOpenFileName(self, "Select GMIC ZIP", "", "ZIP Files (*.zip)")
        if file:
            self.gmic_zip = file
            self.label_zip.setText(f"GMIC archive: {file}")

    def select_krita_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Krita Folder")
        if folder:
            self.krita_folder = folder
            self.label_krita.setText(f"Krita folder: {folder}")

    def install_gmic(self):
        if not self.gmic_zip or not self.krita_folder:
            QMessageBox.warning(self, "Error", "Select both GMIC archive and Krita folder first.")
            return

        # Step 1: Extract GMIC ZIP
        extract_path = os.path.join(os.getcwd(), "gmic_extracted")
        if os.path.exists(extract_path):
            shutil.rmtree(extract_path)
        os.makedirs(extract_path)

        with zipfile.ZipFile(self.gmic_zip, 'r') as zip_ref:
            zip_ref.extractall(extract_path)

        # Step 2: Find GMIC folder inside extracted content
        gmic_folder = None
        for root, dirs, files in os.walk(extract_path):
            if "gmic_krita_qt.dll" in files or "gmic_qt.exe" in files:
                gmic_folder = root
                break

        if not gmic_folder:
            QMessageBox.critical(self, "Error", "GMIC plugin files not found in archive.")
            return

        # Step 3: Find Krita plugin folders
        possible_paths = [
            os.path.join(self.krita_folder, "share", "krita", "pykrita"),
            os.path.join(self.krita_folder, "lib", "krita", "plugins"),
            os.path.join(os.getenv("APPDATA"), "krita", "pykrita")
        ]

        installed = False

        for path in possible_paths:
            if os.path.exists(path):
                try:
                    shutil.copytree(gmic_folder, os.path.join(path, "gmic_qt"), dirs_exist_ok=True)
                    installed = True
                except Exception as e:
                    print("Copy error:", e)

        if not installed:
            QMessageBox.critical(self, "Error", "Could not find a valid Krita plugin folder.")
            return

        # Step 4: Launch Krita
        krita_bin = os.path.join(self.krita_folder, "bin", "krita.exe")
        if os.path.exists(krita_bin):
            subprocess.Popen([krita_bin])
        else:
            QMessageBox.warning(self, "Warning", "GMIC installed, but Krita executable not found.")

        QMessageBox.information(self, "Success", "GMIC successfully installed into Krita!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = GMICInstaller()
    window.show()
    sys.exit(app.exec())

Monday, June 8, 2026

News : Wing Python IDE 12 Early Access.

Wing 12 is now available as an early access release that focuses on AI agent driven development. Wing 12 introduces deep integration with Claude Code, including a dedicated Claude Code tool, a new Tasks tool for planning, executing, and reviewing AI agent work, and a set of MCP servers that allow agents to work more efficiently by giving them access to Wing's source code analysis, unit testing, and debugger features.

Saturday, June 6, 2026

News : The new beta preview of Python 3.15.

Python 3.15 is still in development. This release, 3.15.0b2, is the second of four planned beta releases.
Beta release previews are intended to give the wider community the opportunity to test new features and bug fixes and to prepare their projects to support the new feature release.

Friday, June 5, 2026

News : no more bleach ...

NOTE: 2026-06-05: Bleach is no longer maintained. There will be no future releases including for security issues. See issue: https://github.com/mozilla/bleach/issues/698
Bleach is an allowed-list-based HTML sanitizing library that escapes or strips markup and attributes.
Bleach can also linkify text safely, applying filters that Django’s urlize filter cannot, and optionally setting rel attributes, even on links already in the text.

Wednesday, June 3, 2026

News : Django many security issues.

In accordance with our security release policy, the Django team is issuing releases for Django 6.0.6 and Django 5.2.15. These releases address the security issues detailed below. We encourage all users of Django to upgrade as soon as possible.

Saturday, May 30, 2026

Python 3.10.11 : MiniMax-M2.7 tested with python.

Today, I tested MiniMax-M2.7 with api key from nvidia and openai python package.
The script is one default example:
python -m pip install openai
Collecting openai
  Downloading openai-2.38.0-py3-none-any.whl.metadata (31 kB)
...
Successfully installed distro-1.9.0 jiter-0.15.0 openai-2.38.0
from openai import OpenAI

client = OpenAI(
  base_url = "https://integrate.api.nvidia.com/v1",
  api_key = "nvapi-nvidia"
)

completion = client.chat.completions.create(
  model="minimaxai/minimax-m2.7",
  messages=[{"role":"user","content":""}],
  temperature=1,
  top_p=0.95,
  max_tokens=8192,
  stream=False
)

print(completion.choices[0].message.content)
This will show only:
python minimax_27_001.py
Hello! How can I help you today?
I tested with this python script and works well:
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                             QHBoxLayout, QTextEdit, QLineEdit, QPushButton, QLabel)
from PyQt6.QtCore import QThread, pyqtSignal, Qt
from PyQt6.QtGui import QFont
from openai import OpenAI

# --- WORKER THREAD API ---
# Previne blocarea interfeței grafice în timpul apelului de rețea
class ApiWorker(QThread):
    response_received = pyqtSignal(str)
    error_occurred = pyqtSignal(str)

    def __init__(self, user_message):
        super().__init__()
        self.user_message = user_message

    def run(self):
        try:
            # Inițializare client OpenAI cu endpoint-ul Nvidia specificat de tine
            client = OpenAI(
                base_url="https://integrate.api.nvidia.com/v1",
                api_key="nvapi-KEY"  # nvidia api key
            )

            completion = client.chat.completions.create(
                model="minimaxai/minimax-m2.7",
                messages=[{"role": "user", "content": self.user_message}],
                temperature=1,
                top_p=0.95,
                max_tokens=8192,
                stream=False
            )
            
            # Trimite răspunsul înapoi către fereastra principală
            answer = completion.choices[0].message.content
            self.response_received.emit(answer)
            
        except Exception as e:
            self.error_occurred.emit(str(e))

class MiniMaxChatApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("MiniMax M2.7 - Interactive Chat")
        self.resize(600, 700)

        # Widget-ul central și layout-ul principal
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 1. Zona de afișare a istoricului chat-ului
        self.chat_display = QTextEdit()
        self.chat_display.setReadOnly(True)
        self.chat_display.setFont(QFont("Segoe UI", 11))
        self.chat_display.setPlaceholderText("Conversația va apărea aici...")
        main_layout.addWidget(self.chat_display)

        # 2. Zona de introducere text și butonul (aranjate pe orizontală)
        input_layout = QHBoxLayout()
        
        self.input_field = QLineEdit()
        self.input_field.setFont(QFont("Segoe UI", 11))
        self.input_field.setPlaceholderText("Scrie un mesaj sau o comandă de Windows...")
        # Trimite mesajul când apeși tasta Enter
        self.input_field.returnPressed.connect(self.send_message) 
        input_layout.addWidget(self.input_field)

        self.send_button = QPushButton("Trimite")
        self.send_button.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold))
        self.send_button.clicked.connect(self.send_message)
        input_layout.addWidget(self.send_button)

        main_layout.addLayout(input_layout)

        # 3. Indicator de status (jos de tot)
        self.status_label = QLabel("Pregătit")
        self.status_label.setStyleSheet("color: gray;")
        main_layout.addWidget(self.status_label)

    def send_message(self):
        user_text = self.input_field.text().strip()
        
        if not user_text:
            return  # Nu trimite dacă e gol

        # Adaugă mesajul utilizatorului în istoric și curăță câmpul de input
        self.chat_display.append(f"Tu: {user_text}\n")
        self.input_field.clear()

        # Dezactivează butoanele în timp ce AI-ul se gândește
        self.input_field.setEnabled(False)
        self.send_button.setEnabled(False)
        self.status_label.setText("MiniMax M2.7 gândește...")

        # Pornirea Thread-ului separat pentru apelul API
        self.worker = ApiWorker(user_text)
        self.worker.response_received.connect(self.handle_response)
        self.worker.error_occurred.connect(self.handle_error)
        self.worker.finished.connect(self.cleanup_worker)
        self.worker.start()

    def handle_response(self, ai_response):
        # Afișează răspunsul primit de la MiniMax
        self.chat_display.append(f"MiniMax M2.7: {ai_response}\n")
        self.chat_display.append("-" * 40 + "\n")
        self.status_label.setText("Răspuns primit.")

    def handle_error(self, error_msg):
        # Afișează eroarea în caz că pică netul sau cheia API e greșită
        self.chat_display.append(f"Eroare: {error_msg}\n")
        self.status_label.setText("A apărut o eroare.")

    def cleanup_worker(self):
        # Reatonează butoanele după ce procesul s-a încheiat
        self.input_field.setEnabled(True)
        self.send_button.setEnabled(True)
        self.input_field.setFocus()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    chat_window = MiniMaxChatApp()
    chat_window.show()
    sys.exit(app.exec())
This is the result:

News : ... Django 6.1 alpha 1 released by Jacob Walls on May 20, 2026!

Django 6.1 alpha 1 is now available. It represents the first stage in the 6.1 release cycle and is an opportunity to try out the changes coming in Django 6.1.
Django 6.1 offers a harmonious mélange of new features and usability improvements, which you can read about in the in-development 6.1 release notes.
This alpha milestone marks the feature freeze. The current release schedule calls for a beta release in about a month and a release candidate roughly a month after that. We'll only be able to keep this schedule with early and frequent testing from the community. Updates on the release schedule are available on the Django forum.

Python Qt : Testing new google_news_api released May 22, 2026.

Today, this script will help you too search and find results with google_news_api python package, released: May 22, 2026 by Paolo Mazza. Use your keywords, or make drag and drop ... See the official website - pypi.org.
import sys
import asyncio
import webbrowser
from functools import partial

from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QListWidget,
QListWidgetItem, QPushButton, QTextEdit, QLineEdit, QLabel,
QProgressBar, QFileDialog, QDialog
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal

from google_news_api import AsyncGoogleNewsClient


# ---------------------------------------------------------
# Worker Thread for Async Google News
# ---------------------------------------------------------
class NewsWorker(QThread):
progress = pyqtSignal(int)
finished = pyqtSignal(list)

def __init__(self, queries):
super().__init__()
self.queries = queries

async def fetch_news(self):
results = []
async with AsyncGoogleNewsClient(language="en", country="US") as client:
batch = await client.batch_search(
queries=self.queries,
when="7d",
max_results=10
)

for idx, (topic, articles) in enumerate(batch.items()):
for article in articles:
url = await client.decode_url(article["link"])
results.append({
"title": article["title"],
"source": article["source"],
"url": url
})

self.progress.emit(int((idx + 1) / len(batch) * 100))

return results

def run(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
results = loop.run_until_complete(self.fetch_news())
self.finished.emit(results)


# ---------------------------------------------------------
# HTML Results Dialog
# ---------------------------------------------------------
class ResultsDialog(QDialog):
def __init__(self, results):
super().__init__()
self.setWindowTitle("Search Results")

layout = QVBoxLayout(self)

self.text = QTextEdit()
self.text.setReadOnly(True)
layout.addWidget(self.text)

html = "<h2>Search Results</h2>"
for r in results:
html += f"""
<p>
<b>{r['title']}</b><br>
<i>{r['source']}</i><br>
<a href="{r['url']}">{r['url']}</a>
</p>
"""

self.text.setHtml(html)

save_btn = QPushButton("Save Results")
save_btn.clicked.connect(lambda: self.save_results(html))
layout.addWidget(save_btn)

def save_results(self, html):
file, _ = QFileDialog.getSaveFileName(self, "Save HTML", "", "HTML Files (*.html)")
if not file:
return
with open(file, "w", encoding="utf-8") as f:
f.write(html)


# ---------------------------------------------------------
# Main GUI
# ---------------------------------------------------------
class NewsGUI(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Google News Search Tool")
self.resize(900, 600)

main = QHBoxLayout(self)

# ---------------- LEFT: predefined keywords ----------------
left_layout = QVBoxLayout()
left_layout.addWidget(QLabel("Predefined Keywords"))

self.left_list = QListWidget()
self.left_list.setDragEnabled(True)
self.left_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)

predefined = [
"inteligență artificială", "python", "godot engine", "shaders",
"2D", "3D", "C#", "programming", "catafest",
"Catalin George Festila", "Fălticeni", "Suceava",
"investiții", "2026"
]

for word in predefined:
self.left_list.addItem(word)

left_layout.addWidget(self.left_list)

# Add custom keyword
self.add_keyword_input = QLineEdit()
self.add_keyword_input.setPlaceholderText("Add new keyword...")
left_layout.addWidget(self.add_keyword_input)

add_btn = QPushButton("Add Keyword")
add_btn.clicked.connect(self.add_keyword)
left_layout.addWidget(add_btn)

main.addLayout(left_layout)

# ---------------- RIGHT: search keywords ----------------
right_layout = QVBoxLayout()
right_layout.addWidget(QLabel("Search Keywords (drag from left)"))

self.right_list = QListWidget()
self.right_list.setAcceptDrops(True)
self.right_list.setDragEnabled(True)
self.right_list.setDefaultDropAction(Qt.DropAction.MoveAction)
right_layout.addWidget(self.right_list)

# Custom search input
self.custom_query = QLineEdit()
self.custom_query.setPlaceholderText("Custom search query...")
right_layout.addWidget(self.custom_query)

# Progress bar
self.progress = QProgressBar()
right_layout.addWidget(self.progress)

# Search button
search_btn = QPushButton("Process Search")
search_btn.clicked.connect(self.start_search)
right_layout.addWidget(search_btn)

main.addLayout(right_layout)

# ---------------------------------------------------------
# Add keyword
# ---------------------------------------------------------
def add_keyword(self):
text = self.add_keyword_input.text().strip()
if text:
self.left_list.addItem(text)
self.add_keyword_input.clear()

# ---------------------------------------------------------
# Start search
# ---------------------------------------------------------
def start_search(self):
queries = []

# from right canvas
for i in range(self.right_list.count()):
queries.append(self.right_list.item(i).text())

# custom query
if self.custom_query.text().strip():
queries.append(self.custom_query.text().strip())

if not queries:
QMessageBox.warning(self, "Warning", "No search keywords selected.")
return

self.worker = NewsWorker(queries)
self.worker.progress.connect(self.progress.setValue)
self.worker.finished.connect(self.show_results)
self.worker.start()

# ---------------------------------------------------------
# Show results dialog
# ---------------------------------------------------------
def show_results(self, results):
dlg = ResultsDialog(results)
dlg.exec()


# ---------------------------------------------------------
# MAIN
# ---------------------------------------------------------
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = NewsGUI()
gui.show()
sys.exit(app.exec())
This is the result:

Sunday, May 24, 2026

Python Qt : testing the SUA library using the pytrends and plotly.

People’s interactions can serve as a foundation for certain studies; here is a simple way to process data at low cost using Google Trends and lightweight Python packages such as SUA.
The script retrieves Google Trends search‑interest data for several Romanian economic keywords (such as munca, recesiune, somaj, inflatie, dobanzi) and I used copilot and gemini artificial intelligence. The copilot comes with bad result for fast asking simple scripts. It analyzes the data using the SUA library to estimate simple metrics like interest growth and volatility, then displays the results in a PyQt6 interactive chart where the X‑axis shows real calendar dates and the Y‑axis shows the Google Trends 0–100 normalized interest scale.
However, the script is limited by Google Trends constraints:
Google Trends allows maximum 5 keywords per request, so the script cannot query more terms at once.
Google Trends treats diacritic and non‑diacritic words as different searches (e.g., șomaj ≠ somaj), so the script uses non‑diacritic versions to avoid errors and improve compatibility.
Google Trends returns normalized values (0–100), not real search counts, meaning the data shows relative interest, not absolute volume.
The time series resolution is fixed by Google (weekly data for 12 months), so the X‑axis length depends on how many points Google provides.
These limitations come from Google Trends itself, not from the script.
python -m pip install sua
Collecting sua
...
Successfully installed MarkupSafe-3.0.3 altair-6.1.0 asttokens-3.0.1 attrs-26.1.0 beautifulsoup4-4.14.3 blinker-1.9.0 
cachetools-7.1.4 cffi-2.0.0 clarabel-0.11.1 click-8.4.1 cloudpickle-3.1.2 cmdstanpy-1.3.0 colorama-0.4.6 contourpy-1.3.2 
curl_cffi-0.15.0 cvxpy-1.7.5 cycler-0.12.1 darts-0.44.1 datetime-6.0 decorator-5.3.1 empyrical-0.5.5 executing-2.2.1 
fonttools-4.63.0 fpdf-1.7.2 gitdb-4.0.12 gitpython-3.1.50 holidays-0.97 httptools-0.7.1 importlib_resources-7.1.0 
ipython-8.39.0 itsdangerous-2.2.0 jedi-0.20.0 jinja2-3.1.6 joblib-1.5.3 jsonschema-4.26.0 jsonschema-specifications-2025.9.1 
kiwisolver-1.5.0 llvmlite-0.47.0 lxml-6.1.1 markdown-it-py-4.2.0 matplotlib-3.10.9 matplotlib-inline-0.2.2 mdurl-0.1.2 
multitasking-0.0.13 narwhals-2.21.2 nfoursid-1.0.2 numba-0.65.1 numpy-2.2.6 osqp-1.1.1 pandas-2.3.3 pandas-datareader-0.10.0 
parso-0.8.7 patsy-1.0.2 peewee-4.0.6 pillow-12.2.0 platformdirs-4.9.6 prompt_toolkit-3.0.52 prophet-1.3.0 protobuf-7.35.0 
pure-eval-0.2.3 pyarrow-24.0.0 pycparser-3.0 pydeck-0.9.2 pygments-2.20.0 pyod-3.5.2 pyparsing-3.3.2 pyportfolioopt-1.6.0 
python-dateutil-2.9.0.post0 python-multipart-0.0.29 pytz-2026.2 quantstats-0.0.81 referencing-0.37.0 rich-15.0.0 rpds-py-0.30.0 
scikit-base-0.13.2 scikit-learn-1.7.2 scipy-1.15.3 scs-3.2.11 seaborn-0.13.2 shap-0.49.1 six-1.17.0 slicer-0.0.8 smmap-5.0.3 
soupsieve-2.8.3 stack_data-0.6.3 stanio-0.5.1 starlette-1.1.0 statsmodels-0.14.6 streamlit-1.57.0 sua-1.1.5.1 tabulate-0.10.0 
tenacity-9.1.4 threadpoolctl-3.6.0 toml-0.10.2 tqdm-4.67.3 traitlets-5.15.0 tzdata-2026.2 uvicorn-0.48.0 watchdog-6.0.0 
wcwidth-0.7.0 websockets-16.0 xarray-2025.6.1 yfinance-1.4.0 zope.interface-8.4
python -m pip install pytrends
Collecting pytrends
...
Installing collected packages: pytrends
Successfully installed pytrends-4.9.2
python -m pip install plotly
Collecting plotly
...
Installing collected packages: plotly
...
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed plotly-6.7.0
Now, the plotty use an little tool and this want to access my Scripts folder by environment variables.
If you got this error, then you need to limit the words, because Google returns a 400 Bad Request when your request is not valid for the internal Google Trends API.
    raise exceptions.ResponseError.from_response(response)
pytrends.exceptions.ResponseError: The request failed: Google returned a response with code 400
Let's run teh python script and output is this:
python main.py
Average interest growth (returns):
munca: 0.0072
recesiune: 0.3387
somaj: 0.0308
inflatie: 0.0660
dobanzi: 0.0157

Volatility (risk):
munca: 0.0256
recesiune: 2.9473
somaj: 0.1348
inflatie: 0.1607
dobanzi: 0.0527
The PyQt6 will create this bad result, what 1970 ?:
PyQt_Google_trends_May_2026_2025_bad
The issue in your script occurs because PyQt’s QDateTimeAxis expects the X‑axis timestamp to be expressed in milliseconds (since the Unix epoch), while Python’s .timestamp() function returns the value in seconds. When you send seconds instead of milliseconds, PyQt interprets a value that is 1000 times smaller, which places all chart points somewhere at the beginning of 1970 (right after January 1st, 1970).
Let's fix this issue:
from pytrends.request import TrendReq
# Din moment ce modulele de mai jos erau importate dar nefolosite în codul tău, 
# m-am asigurat că restul rulării rămâne intactă.
try:
    from sua import expected_returns, risk_models
except ImportError:
    pass

from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
from PyQt6.QtGui import QPainter
from PyQt6.QtCore import QPointF, QDateTime, Qt
import sys

# -----------------------------
# 1. Keywords (fără diacritice)
# -----------------------------
keywords = ["munca", "recesiune", "somaj", "inflatie", "dobanzi"]

# -----------------------------
# 2. Preluare date Pytrends
# -----------------------------
pytrends = TrendReq(hl='ro-RO', tz=180)
pytrends.build_payload(keywords, timeframe='today 12-m', geo='RO')

raw = pytrends.interest_over_time()

# Convertim în structuri simple
dates = list(raw.index)  # Python datetime objects
data = {k: list(raw[k]) for k in keywords}

# -----------------------------
# 3. Calcul SUA (randament + risc)
# -----------------------------
returns = {k: [] for k in keywords}

for k in keywords:
    series = data[k]
    for i in range(1, len(series)):
        prev = series[i-1] or 1
        curr = series[i] or 1
        returns[k].append((curr - prev) / prev)

mu = {k: sum(returns[k]) / len(returns[k]) for k in keywords}

def covariance(a, b):
    mean_a = sum(a) / len(a)
    mean_b = sum(b) / len(b)
    return sum((a[i]-mean_a)*(b[i]-mean_b) for i in range(len(a))) / len(a)

S = {k: covariance(returns[k], returns[k]) for k in keywords}

print("Average interest growth (returns):")
for k, v in mu.items():
    print(f"{k}: {v:.4f}")

print("\nVolatility (risk):")
for k, v in S.items():
    print(f"{k}: {v:.4f}")

# -----------------------------
# 4. PyQt6 Chart GUI cu date reale (CORECTAT)
# -----------------------------
class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Google Trends Economic Interest (RO)")

        chart = QChart()
        chart.setTitle("Interest Over Time (Google Trends Romania)")

        # Axa X = date reale
        axis_x = QDateTimeAxis()
        axis_x.setFormat("yyyy-MM-dd")
        axis_x.setTitleText("Date")
        axis_x.setTickCount(10)

        # !!! CORECȚIE 1: Să îi spunem axei care este limita minimă și maximă reală !!!
        # Conversie din primul și ultimul obiect datetime din Pytrends în QDateTime
        qt_start_date = QDateTime.fromMSecsSinceEpoch(int(dates[0].timestamp() * 1000))
        qt_end_date = QDateTime.fromMSecsSinceEpoch(int(dates[-1].timestamp() * 1000))
        axis_x.setRange(qt_start_date, qt_end_date)

        # Axa Y = valori 0–100
        axis_y = QValueAxis()
        axis_y.setRange(0, 100)
        axis_y.setTitleText("Interest (0–100)")

        chart.addAxis(axis_x, Qt.AlignmentFlag.AlignBottom)
        chart.addAxis(axis_y, Qt.AlignmentFlag.AlignLeft)

        # Adăugăm seriile
        for k in keywords:
            series = QLineSeries()
            series.setName(k)

            for i, val in enumerate(data[k]):
                # !!! CORECȚIE 2: Înmulțim cu 1000 pentru a transforma în milisecunde !!!
                timestamp_ms = int(dates[i].timestamp() * 1000)
                series.append(QPointF(float(timestamp_ms), float(val)))

            chart.addSeries(series)
            series.attachAxis(axis_x)
            series.attachAxis(axis_y)

        view = QChartView(chart)
        view.setRenderHint(QPainter.RenderHint.Antialiasing)

        self.setCentralWidget(view)

app = QApplication(sys.argv)
window = Window()
window.resize(1400, 700)
window.show()
sys.exit(app.exec())
This result is good and is fixed by Gemnini artificial intelligence, but if you want real development then you need to use more then simple issue to artificial intelligence:
python main.py
Average interest growth (returns):
munca: 0.0073
recesiune: 0.3387
somaj: 0.0308
inflatie: 0.0660
dobanzi: 0.0189

Volatility (risk):
munca: 0.0258
recesiune: 2.9473
somaj: 0.1348
inflatie: 0.1607
dobanzi: 0.0594
PyQt_Google_trends_May_2026_2025_good

Thursday, May 21, 2026

Python 3.10.11 : testing zernio social platform with python.

Today, I get two free account on the zernio webpage.
This python script does three things:
First calls Zernio by sends GET https://zernio.com/api/v1/accounts using your Bearer token and loads the JSON response.
Then filters YouTube accounts: from the returned accounts[] list, it keeps only the items that look like YouTube (based on fields like platform/provider/network == "youtube").
Last one, prints and searches identifiers:
It prints the full JSON object for each connected YouTube account (first ~4000 characters), so you can see what fields Zernio returns.
It recursively scans that object for any string that matches a YouTube Channel ID pattern (strings starting with UC...) and prints the path and value for each match (e.g., meta.channelId = UCxxxx).
The result returns by printing to the console:
how many YouTube accounts Zernio returned.
the JSON for each YouTube account object.
a list of candidate channelId strings (and where they appear in the JSON).
Let's see the script:
import requests
import json
import re

ZERNIO_TOKEN = "sk_API_KEY"
API_BASE = "https://zernio.com/api/v1"

headers = {
    "Authorization": f"Bearer {ZERNIO_TOKEN}",
    "Accept": "application/json",
}

def is_youtube_account(acc: dict) -> bool:
    for k in ("platform", "provider", "network"):
        v = acc.get(k)
        if isinstance(v, str) and v.lower() == "youtube":
            return True
    return False

def find_uc_strings(obj, path=""):
    hits = []
    if isinstance(obj, dict):
        for k, v in obj.items():
            hits += find_uc_strings(v, f"{path}.{k}" if path else k)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            hits += find_uc_strings(v, f"{path}[{i}]")
    elif isinstance(obj, str):
        # Typical YouTube channel id: starts with UC and is 24 chars, but we’ll be flexible
        if re.match(r"^UC[a-zA-Z0-9_-]{10,}$", obj):
            hits.append((path, obj))
    return hits

resp = requests.get(f"{API_BASE}/accounts", headers=headers, timeout=30)
resp.raise_for_status()
data = resp.json()

accounts = data.get("accounts", [])
yt = [a for a in accounts if is_youtube_account(a)]

print(f"Found {len(yt)} YouTube accounts in Zernio.\n")

for idx, acc in enumerate(yt):
    print(f"--- YouTube account #{idx} full object ---")
    print(json.dumps(acc, ensure_ascii=False, indent=2)[:4000])  # first 4000 chars
    print()

    hits = find_uc_strings(acc)
    print("Possible channelId candidates (paths):")
    for p, v in hits:
        print(f"  - {p} = {v}")
    print()
This is a part of result:
...
  "displayName": "Cătălin George Feștilă",
  "enabled": true,
  "externalPostCount": 63,
  "followersCount": 110,
  "followersLastUpdated": "2026-05-21T18:31:51.857Z",
  "gcpProjectId": "default",
  "intentionalDisconnectAt": null,
  "isActive": true,
...
Possible channelId candidates (paths):
  - metadata.profileData.id = UC2Dv01HhPCb8Obb9IxO81Jw
  - platformUserId = UC2Dv01HhPCb8Obb9IxO81Jw

Sunday, May 17, 2026

Python Qt : network tool with PyQt6-Charts.

Today, this python script will create a tool for network with PyQt6-Charts.
You need to install the PyQt6-Charts:
python.exe -m pip install PyQt6-Charts
Collecting PyQt6-Charts
...
Installing collected packages: PyQt6-Charts-Qt6, PyQt6-Charts
Successfully installed PyQt6-Charts-6.11.0 PyQt6-Charts-Qt6-6.11.1
Let's see the python script.
import sys
import psutil
from pathlib import Path

from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
    QTableWidget, QTableWidgetItem, QHeaderView
)
from PyQt6.QtCore import QTimer, Qt
from PyQt6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis


def bytes_to_human(n: int) -> str:
    symbols = ('B', 'KB', 'MB', 'GB', 'TB')
    prefix = {}
    for i, s in enumerate(symbols[1:], 1):
        prefix[s] = 1 << (i * 10)
    for s in reversed(symbols[1:]):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return f"{value:.2f} {s}"
    return f"{n} B"


class NetworkMonitor(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Network Monitor – PyQt6 (Traffic + Ports)")
        self.resize(1000, 650)

        main_layout = QVBoxLayout(self)

        # ---------------- TOP LABELS ----------------
        self.label_up = QLabel("Upload: 0 B/s")
        self.label_down = QLabel("Download: 0 B/s")

        top_layout = QHBoxLayout()
        top_layout.addWidget(self.label_up)
        top_layout.addWidget(self.label_down)
        main_layout.addLayout(top_layout)

        # ---------------- CHART ----------------
        self.series_up = QLineSeries()
        self.series_down = QLineSeries()
        self.series_up.setName("Upload")
        self.series_down.setName("Download")

        self.chart = QChart()
        self.chart.addSeries(self.series_up)
        self.chart.addSeries(self.series_down)
        self.chart.setTitle("Network traffic (bytes/sec)")

        # Axes (Qt6 style)
        self.axis_x = QValueAxis()
        self.axis_y = QValueAxis()

        self.axis_x.setRange(0, 60)
        self.axis_y.setRange(0, 1024 * 1024)  # 1 MB/s default
        self.axis_x.setTitleText("Time (s)")
        self.axis_y.setTitleText("Bytes / second")

        self.chart.addAxis(self.axis_x, Qt.AlignmentFlag.AlignBottom)
        self.chart.addAxis(self.axis_y, Qt.AlignmentFlag.AlignLeft)

        self.series_up.attachAxis(self.axis_x)
        self.series_up.attachAxis(self.axis_y)
        self.series_down.attachAxis(self.axis_x)
        self.series_down.attachAxis(self.axis_y)

        self.chart_view = QChartView(self.chart)
        main_layout.addWidget(self.chart_view)

        # ---------------- PORTS TABLE ----------------
        self.table = QTableWidget(0, 5)
        self.table.setHorizontalHeaderLabels(
            ["Local IP:Port", "Remote IP:Port", "Status", "PID", "Process"]
        )
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)

        # 🔥 Sortare activată
        self.table.setSortingEnabled(True)

        main_layout.addWidget(self.table)

        # ---------------- STATE ----------------
        self.old_stats = psutil.net_io_counters()
        self.x_pos = 0

        # ---------------- TIMER ----------------
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_stats)
        self.timer.start(1000)  # 1s

    # ---------------------------------------------------------
    def update_stats(self):
        # --- traffic ---
        new_stats = psutil.net_io_counters()
        sent = new_stats.bytes_sent - self.old_stats.bytes_sent
        recv = new_stats.bytes_recv - self.old_stats.bytes_recv
        self.old_stats = new_stats

        self.label_up.setText(f"Upload: {bytes_to_human(sent)}/s")
        self.label_down.setText(f"Download: {bytes_to_human(recv)}/s")

        # --- chart data ---
        self.series_up.append(self.x_pos, sent)
        self.series_down.append(self.x_pos, recv)
        self.x_pos += 1

        # keep last 60 seconds visible
        if self.x_pos > 60:
            self.axis_x.setRange(self.x_pos - 60, self.x_pos)

        # auto-scale Y a bit
        max_val = max(sent, recv, 1)
        current_max = self.axis_y.max()
        if max_val > current_max * 0.9:
            self.axis_y.setRange(0, max_val * 1.5)

        # --- ports ---
        self.update_ports()

    # ---------------------------------------------------------
    def update_ports(self):
        conns = psutil.net_connections(kind="tcp")

        self.table.setSortingEnabled(False)  # prevenim flicker
        self.table.setRowCount(0)

        for c in conns:
            if not c.laddr:
                continue

            row = self.table.rowCount()
            self.table.insertRow(row)

            # Local
            local = f"{c.laddr.ip}:{c.laddr.port}"
            item_local = QTableWidgetItem(local)
            item_local.setData(Qt.ItemDataRole.UserRole, c.laddr.port)

            # Remote
            if c.raddr:
                remote = f"{c.raddr.ip}:{c.raddr.port}"
                remote_port = c.raddr.port
            else:
                remote = "-"
                remote_port = -1

            item_remote = QTableWidgetItem(remote)
            item_remote.setData(Qt.ItemDataRole.UserRole, remote_port)

            # Status
            item_status = QTableWidgetItem(c.status)

            # PID
            pid = c.pid if c.pid else -1
            item_pid = QTableWidgetItem(str(pid))
            item_pid.setData(Qt.ItemDataRole.UserRole, pid)

            # Process name
            proc_name = "-"
            if c.pid:
                try:
                    proc_name = psutil.Process(c.pid).name()
                except:
                    proc_name = "?"

            item_proc = QTableWidgetItem(proc_name)

            self.table.setItem(row, 0, item_local)
            self.table.setItem(row, 1, item_remote)
            self.table.setItem(row, 2, item_status)
            self.table.setItem(row, 3, item_pid)
            self.table.setItem(row, 4, item_proc)

        self.table.setSortingEnabled(True)  # reactivăm sortarea


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = NetworkMonitor()
    w.show()
    sys.exit(app.exec())

Saturday, May 16, 2026

Python 3.10.11 : about the windows embeddable portable Python distributions fix pip.

Let's learn about the windows embeddable portable Python distributions.
  • The Windows embeddable Python distribution is a minimal, self‑contained build of Python designed to run entirely from its own directory without installation.
  • This distribution does not modify system settings, environment variables, or the Windows registry.
  • Its structure makes it suitable for embedding Python inside applications or distributing Python as a portable runtime.
Typical use cases
  • Bundling Python with standalone software that requires a predictable runtime environment.
  • Running Python scripts in isolated environments where system‑wide installations must not be affected.
  • Deploying portable utilities that must operate from removable storage or restricted systems.
When the embeddable distribution is not ideal
  • General development workflows that rely on pip, external packages, or virtual environments.
  • Educational or experimental setups where tutorials assume a standard Python installation.
  • Projects that depend on automatic module discovery and dynamic package management.
The Role of python310._pth
  • The file named python310._pth controls how the embeddable distribution locates and loads Python modules.
  • When this file is present, Python enters an isolated mode in which only the paths explicitly listed inside the file are used.
  • If the file does not include the line import site, the standard site initialization process is disabled, preventing access to site‑packages.
Typical structure of python310._pth
python310.zip
.
import site
Explanation of each entry
  • python310.zip specifies the location of the standard library packaged as a zip archive.
  • . allows Python to import modules from the root directory of the distribution.
  • import site activates the site module, enabling automatic loading of Lib and site‑packages.
Enabling pip and external modules
  • The embeddable distribution does not load external modules unless the appropriate paths are added to python310._pth.
  • To enable pip and other installed packages, the file must include the Lib and Lib\site-packages directories.
Example of a Fully Enabled python310._pth
python310.zip
.
Lib
Lib\site-packages
import site
Testing the updated configuration
python -c "import sys; print(sys.path)"
Installing pip after enabling site‑packages
  • Once the module paths are active, pip can be installed using standard methods.
python get-pip.py
python -m ensurepip
Verifying pip
python -m pip --version
Advantages of the embeddable distribution:
  • Provides a predictable and isolated runtime environment.
  • Does not interfere with system‑wide Python installations.
  • Ideal for packaging Python with standalone applications.
Disadvantages of the embeddable distribution:
  • pip and external modules are disabled by default.
  • Requires manual configuration to behave like a standard installation.
  • Not suitable for typical development workflows.
Clean, Ready‑to‑Use python310._pth File
python310.zip
.
Lib
Lib\site-packages
import site
This will fix the embeddable distribution, let's use this source code to fix the pip tool:
import os
import urllib.request
import zipfile
import shutil

PYTHON_DIR = r"C:\python-3_10_11"
SITE = fr"{PYTHON_DIR}\Lib\site-packages"

print("[INFO] Descarc pip.zip...")
urllib.request.urlretrieve(
    "https://github.com/pypa/pip/archive/refs/heads/main.zip",
    "pip.zip"
)

print("[INFO] Dezarhivez pip.zip...")
with zipfile.ZipFile("pip.zip", "r") as z:
    z.extractall("pip_src")

pip_src = "pip_src/pip-main/src/pip"

print("[INFO] Copiez pip în site-packages...")
target = os.path.join(SITE, "pip")
if os.path.exists(target):
    shutil.rmtree(target)

shutil.copytree(pip_src, target)

print("[INFO] Creez pip.dist-info minimal...")
dist = os.path.join(SITE, "pip.dist-info")
os.makedirs(dist, exist_ok=True)

with open(os.path.join(dist, "METADATA"), "w") as f:
    f.write("Name: pip\nVersion: 0\n")

print("[OK] pip instalat direct în Python.")
print("Rulează acum:")
print("   python -m pip --version")
Let's tun and test with PyQt6:
python fix_pip.py
[INFO] Descarc pip.zip...
[INFO] Dezarhivez pip.zip...
[INFO] Copiez pip în site-packages...
[INFO] Creez pip.dist-info minimal...
[OK] pip instalat direct în Python.
Rulează acum:
   python -m pip --version

python -m pip --version
pip 26.2.dev0 from C:\python-3_10_11\Lib\site-packages\pip (python 3.10)

python -m pip install PyQt6
Collecting PyQt6
  Downloading pyqt6-6.11.0-cp310-abi3-win_amd64.whl.metadata (2.2 kB)
...
Installing collected packages: PyQt6-Qt6, PyQt6-sip, PyQt6
Successfully installed PyQt6-6.11.0 PyQt6-Qt6-6.11.1 PyQt6-sip-13.11.1