These release notes cover the new features, as well as some backwards incompatible changes you’ll want to be aware of when upgrading from Django 6.0 or earlier. We’ve begun the deprecation process for some features.
See the official website.
Python tutorials with source code, examples, guides, and tips and tricks for Windows and Linux development.

import tkinter as tk
from tkinter import messagebox, ttk
import webbrowser
import feedparser
RSS_URL = "https://blog.python.org/feeds/posts/default"
article_links = []
def clean_text(text):
"""Converts text to basic ASCII to strip non-supported Android Tkinter characters."""
if not text:
return ""
# Encodes to ASCII ignoring emojis, special quotes, and unsupported byte sequences
clean = text.encode("ascii", "ignore").decode("ascii")
# Removes extra whitespace and linebreaks
return " ".join(clean.split())
def load_feed():
global article_links
listbox.delete(0, tk.END)
article_links.clear()
status_label.config(text="Fetching articles...", foreground="blue")
root.update()
try:
feed = feedparser.parse(RSS_URL)
if not feed.entries:
status_label.config(text="No articles found.", foreground="red")
return
for entry in feed.entries:
title_raw = entry.get("title", "Untitled")
title = clean_text(title_raw)
link = entry.get("link", "")
listbox.insert(tk.END, title)
article_links.append(link)
status_label.config(
text=f"Loaded {len(feed.entries)} articles!", foreground="green"
)
except Exception as e:
status_label.config(text="Connection error!", foreground="red")
messagebox.showerror("Error", f"Failed to load feed:\n{e}")
def open_link(event):
try:
index = listbox.curselection()[0]
url = article_links[index]
if url:
webbrowser.open(url)
except IndexError:
pass
# --- GUI Setup ---
root = tk.Tk()
root.title("Python Foundation RSS Reader")
root.geometry("600x800")
title_label = ttk.Label(
root, text="Python Software Foundation Blog", font=("Helvetica", 14, "bold")
)
title_label.pack(pady=10)
refresh_btn = ttk.Button(root, text="Refresh", command=load_feed)
refresh_btn.pack(pady=5)
status_label = ttk.Label(
root, text="Connecting...", font=("Helvetica", 10)
)
status_label.pack(pady=5)
frame = ttk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL)
listbox = tk.Listbox(
frame,
font=("Helvetica", 11),
selectbackground="#007ACC",
selectforeground="white",
activestyle="none",
yscrollcommand=scrollbar.set,
bd=1,
relief="solid",
)
scrollbar.config(command=listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
listbox.bind("<>", open_link)
root.after(500, load_feed)
root.mainloop()

import os
import platform
import subprocess
import sys
import tkinter as tk
from tkinter import ttk
def get_android_prop(prop_name):
"""Citește o proprietate de sistem Android folosind comanda getprop."""
try:
val = (
subprocess.check_output(f"getprop {prop_name}", shell=True, text=True)
.strip()
)
return val if val else "N/A"
except Exception:
return "Indisponibil"
def get_system_info():
"""Colectează informații extinse despre telefon și modulul Tau."""
# 1. Verificare modul Tau
tau_info = "Neinstalat"
try:
import tau
tau_info = getattr(tau, "__version__", "Instalat (fără __version__)")
except ImportError:
tau_info = "Pachetul 'tau-ai' nu este instalat"
# 2. Preluare date extinse Android
model = get_android_prop("ro.product.model")
brand = get_android_prop("ro.product.brand")
manufacturer = get_android_prop("ro.product.manufacturer")
android_version = get_android_prop("ro.build.version.release")
sdk_version = get_android_prop("ro.build.version.sdk")
device_board = get_android_prop("ro.product.board")
hardware = get_android_prop("ro.hardware")
info = {
"Versiune Tau": tau_info,
"Producător": f"{manufacturer.capitalize()} ({brand.capitalize()})",
"Model Telefon": model,
"Versiune Android": f"Android {android_version} (API {sdk_version})",
"Placă / Hardware": f"{device_board} / {hardware}",
"Arhitectură CPU": platform.machine(),
"Sistem Python": f"{platform.system()} {platform.release()}",
"Versiune Python": sys.version.split()[0],
}
return info
def main():
root = tk.Tk()
root.title("Tau & Detalii Android")
root.geometry("420x560")
root.configure(bg="#212121")
title = tk.Label(
root,
text="Informații Dispozitiv & Tau",
font=("Helvetica", 15, "bold"),
fg="#00E676",
bg="#212121",
pady=12,
)
title.pack()
# Zonă de text pentru date
text_area = tk.Text(
root,
font=("Courier", 10),
bg="#303030",
fg="#FFFFFF",
padx=10,
pady=10,
relief=tk.FLAT,
)
text_area.pack(fill=tk.BOTH, expand=True, padx=15, pady=5)
# Inserare date în fereastră
info_data = get_system_info()
text_content = "=== SPECIFICAȚII TELEFON & APP ===\n\n"
for key, value in info_data.items():
text_content += f"• {key}:\n {value}\n\n"
text_area.insert(tk.END, text_content)
text_area.config(state=tk.DISABLED)
# Buton de închidere
btn_close = tk.Button(
root,
text="Închide",
font=("Helvetica", 11, "bold"),
bg="#FF5252",
fg="white",
activebackground="#FF1744",
activeforeground="white",
command=root.destroy,
pady=8,
)
btn_close.pack(fill=tk.X, padx=15, pady=15)
root.mainloop()
if __name__ == "__main__":
main()





import os
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime, timezone
def draw_and_save_sky_map():
# Coordonate Fălticeni, România
LAT = 47.46
LON = 26.30
# Calendarul evenimentelor până la sfârșitul anului 2026
events = [
{
'name': 'Perseide (Vârf)',
'date_str': '12 Aug',
'rise_time': '22:00',
'ra': 3.07, 'dec': 58.0,
'color': '#00ffcc', 'marker': '*'
},
{
'name': 'Orionide (Vârf)',
'date_str': '21 Oct',
'rise_time': '23:30',
'ra': 6.34, 'dec': 16.0,
'color': '#ff9900', 'marker': '*'
},
{
'name': 'Leonide (Vârf)',
'date_str': '17 Noi',
'rise_time': '00:15',
'ra': 10.2, 'dec': 22.0,
'color': '#ff3366', 'marker': '*'
},
{
'name': 'Geminide (Vârf)',
'date_str': '13 Dec',
'rise_time': '20:30',
'ra': 7.46, 'dec': 33.0,
'color': '#ffff00', 'marker': '*'
},
{
'name': 'Asteroid NEO 2026',
'date_str': 'Toamnă 2026',
'rise_time': '21:00',
'ra': 14.5, 'dec': 10.0,
'color': '#ff00ff', 'marker': 's'
}
]
# Calcul Timp Sidereal Local (LST)
now_utc = datetime.now(timezone.utc)
j2000_epoch = datetime(2000, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
days_since_j2000 = (now_utc - j2000_epoch).total_seconds() / 86400.0
lst = (18.697374558 + 24.06570982441908 * days_since_j2000 + LON / 15.0) % 24
# Creare grafic Matplotlib (Polar)
fig = plt.figure(figsize=(8, 8), facecolor='#0b0d17')
ax = fig.add_subplot(111, polar=True, facecolor='#05070f')
# Orientare fixă: NORDUL SUS (0°)
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_rlim(0, 90)
ax.set_yticklabels([])
lat_rad = np.radians(LAT)
for item in events:
ha = (lst - item['ra']) * 15.0
ha_rad = np.radians(ha)
dec_rad = np.radians(item['dec'])
sin_alt = np.sin(dec_rad) * np.sin(lat_rad) + np.cos(dec_rad) * np.cos(lat_rad) * np.cos(ha_rad)
alt = np.degrees(np.arcsin(np.clip(sin_alt, -1.0, 1.0)))
if alt <= 5:
alt = 35.0
cos_az = (np.sin(dec_rad) - np.sin(lat_rad) * sin_alt) / (np.cos(lat_rad) * np.sin(np.arccos(sin_alt)))
az = np.degrees(np.arccos(np.clip(cos_az, -1.0, 1.0)))
if np.sin(ha_rad) > 0:
az = 360 - az
theta = np.radians(az)
r = 90 - alt
ax.plot(theta, r, marker=item['marker'], markersize=12, color=item['color'])
label_text = f"{item['name']}\n[{item['date_str']}]\nRăsare ~{item['rise_time']}"
ax.text(theta, r + 7, label_text, color=item['color'],
fontsize=7.5, ha='center', fontweight='bold')
cardinals = [('NORD (0°)', 0), ('EST (90°)', 90), ('SUD (180°)', 180), ('VEST (270°)', 270)]
for label, angle in cardinals:
ax.text(np.radians(angle), 102, label, color='white', fontsize=9, fontweight='bold', ha='center', va='center')
ax.set_title("HARTĂ EVENIMENTE ASTRONOMICE 2026 (Aug - Dec)\nLocație: Fălticeni | Orientare: Nordul Sus\n",
color='white', fontsize=9, pad=15)
ax.grid(color='#1a233a', linestyle=':', linewidth=0.8)
plt.tight_layout()
# --- SALVARE IMAGINE ÎN FOLDERUL DOWNLOAD ---
download_path = "/sdcard/Download"
# Alternativă în caz că calea /sdcard nu este mapată la fel pe unele versiuni de Android
if not os.path.exists(download_path):
download_path = os.path.expanduser("~/storage/downloads")
file_name = f"harta_astronomica_2026_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
full_path = os.path.join(download_path, file_name)
try:
plt.savefig(full_path, dpi=300, facecolor=fig.get_facecolor(), edgecolor='none')
print(f" Imaginea a fost salvată cu succes la:\n{full_path}")
except Exception as e:
print(f" Eroare la salvarea fișierului: {e}")
plt.show()
if __name__ == "__main__":
draw_and_save_sky_map()
python -m pip install pyui
Collecting pyui
Downloading pyui-0.1.0-py3-none-any.whl.metadata (893 bytes)
Collecting PySDL2>=0.9.7 (from pyui)
Downloading PySDL2-0.9.17-py3-none-any.whl.metadata (3.8 kB)
Downloading pyui-0.1.0-py3-none-any.whl (6.0 MB)
---------------------------------------- 6.0/6.0 MB 6.0 MB/s 0:00:01
Downloading PySDL2-0.9.17-py3-none-any.whl (583 kB)
---------------------------------------- 583.1/583.1 kB 2.1 MB/s 0:00:00
Installing collected packages: PySDL2, pyui
Successfully installed PySDL2-0.9.17 pyui-0.1.0
python -m pip install pysdl2-dll
Collecting pysdl2-dll
Downloading pysdl2_dll-2.32.10-py2.py3-none-win_amd64.whl.metadata (4.7 kB)
Downloading pysdl2_dll-2.32.10-py2.py3-none-win_amd64.whl (4.1 MB)
---------------------------------------- 4.1/4.1 MB 4.1 MB/s 0:00:01
Installing collected packages: pysdl2-dll
Successfully installed pysdl2-dll-2.32.10
import pyui
class ItemGridView(pyui.View):
def content(self):
yield pyui.ScrollView(axis=self.axis)(
pyui.Grid(num=self.num, size=self.size, axis=self.axis, flex=self.flex)(
pyui.ForEach(
range(self.item_count),
lambda num: (
pyui.Rectangle()(pyui.Text(num + 1).color(255, 255, 255))
.background(120, 120, 120)
.radius(5)
.animate()
),
)
)
)
class GridTest(pyui.View):
axis = pyui.State(default=1)
item_count = pyui.State(int, default=50)
size = pyui.State(default=100)
num = pyui.State(default=4)
size_or_num = pyui.State(default=0)
flex = pyui.State(default=False)
def content(self):
if self.size_or_num.value == 0:
size = None
num = self.num.value
else:
size = self.size.value
num = None
yield pyui.HStack(alignment=pyui.Alignment.LEADING)(
pyui.VStack(alignment=pyui.Alignment.LEADING)(
pyui.Text("Axis"),
pyui.SegmentedButton(self.axis)(
pyui.Text(pyui.Axis.HORIZONTAL.name),
pyui.Text(pyui.Axis.VERTICAL.name),
),
pyui.HStack(
pyui.Text("Number of items"),
pyui.Spacer(),
pyui.Text(self.item_count.value)
.color(128, 128, 128)
.priority("high"),
),
pyui.Slider(self.item_count, maximum=200),
pyui.Text("Fill rows/columns by"),
pyui.SegmentedButton(self.size_or_num)(
pyui.Text("Number"),
pyui.Text("Size"),
),
pyui.HStack(
pyui.Text("Items per row/column"),
pyui.Spacer(),
pyui.Text(self.num.value).color(128, 128, 128).priority("high"),
),
pyui.Slider(self.num, minimum=1, maximum=10).disable(
self.size_or_num.value == 1
),
pyui.HStack(
pyui.Text("Item size"),
pyui.Spacer(),
pyui.Text(self.size.value).color(128, 128, 128).priority("high"),
),
pyui.Slider(self.size, minimum=50, maximum=200).disable(
self.size_or_num.value == 0
),
pyui.Toggle(self.flex, label="Adjust size to fit").disable(
self.size_or_num.value == 0
),
)
.padding(10)
.size(width=300),
ItemGridView(
item_count=self.item_count.value,
size=size,
num=num,
axis=self.axis.value,
flex=self.flex.value,
),
)
if __name__ == "__main__":
app = pyui.Application("io.temp.GridTest")
app.window("Grid Tester", GridTest())
app.run()import sys
import requests
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit, QPushButton, QLabel
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
# PUNE TOKEN-UL TAU AICI, CA STRING
BROWSERLESS_TOKEN = "fill_TOKEN_official__website"
class BrowserlessViewer(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Browserless Web Screenshot Viewer")
self.resize(900, 700)
layout = QVBoxLayout(self)
self.url_edit = QLineEdit()
self.url_edit.setPlaceholderText("Introdu adresa web...")
layout.addWidget(self.url_edit)
self.btn = QPushButton("Generează Screenshot")
self.btn.clicked.connect(self.take_screenshot)
layout.addWidget(self.btn)
self.canvas = QLabel()
self.canvas.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.canvas)
def take_screenshot(self):
url = self.url_edit.text().strip()
if not url:
return
api_url = f"https://chrome.browserless.io/screenshot?token={BROWSERLESS_TOKEN}"
payload = {
"url": url,
"options": {
"fullPage": True
}
}
try:
r = requests.post(api_url, json=payload)
r.raise_for_status()
pixmap = QPixmap()
pixmap.loadFromData(r.content)
self.canvas.setPixmap(pixmap)
except Exception as e:
print("Eroare:", e)
if __name__ == "__main__":
app = QApplication(sys.argv)
viewer = BrowserlessViewer()
viewer.show()
sys.exit(app.exec())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()