import os
def bulk_rename(folder_path, old_name_part, new_name_part):
if not os.path.isdir(folder_path):
raise NotADirectoryError(f"Путь «{folder_path}» не найден")
for filename in os.listdir(folder_path):
if old_name_part in filename:
new_filename = filename.replace(old_name_part, new_name_part)
old_full = os.path.join(folder_path, filename)
new_full = os.path.join(folder_path, new_filename)
# Пропускаем, если имена совпадают
if old_full == new_full:
print(f"Пропущено (имена совпадают): {filename}")
continue
try:
os.rename(old_full, new_full)
print(f"Переименовано: {filename} → {new_filename}")
except OSError as e:
print(f"Ошибка при переименовании {filename}: {e}")
if __name__ == "__main__":
folder = '/path/to/your/folder'
bulk_rename(folder, 'old_part', 'new_part')
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍1
molecule 🟠 Поддержка Docker, Vagrant, Podman, EC2 и других драйверов🟠 Интеграция с Ansible, Testinfra, Goss🟠 Автоматические проверки, линтинг и CI🟠 Подходит для TDD в инфраструктуре
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍1
Rich-click🟠 Улучшает UX CLI-приложений без переписывания кода🟠 Делает утилиты выглядящими профессионально сразу🟠 Полезен для инструментов разработчика, DevOps-скриптов, Open Source🟠 Минимальные усилия → максимальный эффект
pip install rich-click
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2🔥2
testinfra 🟠 Пишется на Python, использует pytest🟠 Проверка состояния системы: файлов, пакетов, сервисов🟠 Работает через SSH, Docker, локально и др.🟠 Идеален для DevOps, CI/CD, Ansible и Molecule
pip install testinfra
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥3
requests, argparse, os, re🟠 Получает список ссылок на видео YouTube🟠 Извлекает video ID с помощью регулярных выражений🟠 Загружает максимальную по доступности миниатюру (maxresdefault.jpg или hqdefault.jpg)🟠 Сохраняет в указанную директорию со стандартным именем
python yt_thumb_downloader.py --urls video_urls.txt --dir thumbnails
import requests
import argparse
import os
import re
YOUTUBE_THUMB_URL = "https://img.youtube.com/vi/{}/maxresdefault.jpg"
FALLBACK_URL = "https://img.youtube.com/vi/{}/hqdefault.jpg"
ID_PATTERN = r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})"
def extract_id(url):
m = re.search(ID_PATTERN, url)
return m.group(1) if m else None
def download_thumb(video_id, save_dir):
url = YOUTUBE_THUMB_URL.format(video_id)
r = requests.get(url)
if r.status_code != 200:
url = FALLBACK_URL.format(video_id)
r = requests.get(url)
if r.status_code == 200:
path = os.path.join(save_dir, f"{video_id}.jpg")
with open(path, "wb") as f:
f.write(r.content)
print(f"✅ Downloaded {path}")
else:
print(f"❌ Failed thumbnail for {video_id}")
def main(urls_file, save_dir):
os.makedirs(save_dir, exist_ok=True)
with open(urls_file, encoding="utf-8") as f:
for line in f:
url = line.strip()
vid = extract_id(url)
if vid:
download_thumb(vid, save_dir)
else:
print(f"❌ Can't extract ID from {url}")
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--urls", required=True, help="Файл со ссылками YouTube")
p.add_argument("--dir", required=True, help="Папка для сохранения")
args = p.parse_args()
main(args.urls, args.dir)
pip install requests
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤7🔥5👍4
vault-cli🟠 Чтение/запись секретов через Python или CLI🟠 Поддержка Vault API (v1)🟠 Можно использовать в CI/CD пайплайнах🟠 Конфигурация через YAML/ENV-переменные
pip install vault-cli
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3🔥2
subprocess, boto3, os, time🟠 Выполняет mysqldump через subprocess🟠 Архивирует папку или файл дампа в .tar.gz с таймстампом🟠 Загружает архив в указанный S3‑бакет через boto3🟠 Обрабатывает ошибки доступа и отсутствия файла🟠 Удобен для cron‑запусков и CI/CD интеграции
python backup_mysql_s3.py \
--host localhost --user root --password pwd \
--db mydatabase --bucket my-s3-bucket --dest backups/
import subprocess, boto3, time, os
from botocore.exceptions import NoCredentialsError
def dump_db(host, user, password, db, out_file):
cmd = [
"mysqldump", "-h", host, "-u", user, f"-p{password}", db
]
with open(out_file, "wb") as f:
subprocess.run(cmd, stdout=f, check=True)
def archive(src, dest_dir):
ts = time.strftime("%Y%m%d%H%M%S")
archive_name = os.path.join(dest_dir, f"backup_{ts}.tar.gz")
subprocess.run(["tar", "-czf", archive_name, src], check=True)
return archive_name
def upload_s3(file_path, bucket, key=None):
s3 = boto3.client("s3")
if key is None:
key = os.path.basename(file_path)
try:
s3.upload_file(file_path, bucket, key)
print(f"✅ Backup uploaded to s3://{bucket}/{key}")
except FileNotFoundError:
print("❌ File not found:", file_path)
except NoCredentialsError:
print("❌ AWS credentials are missing")
def main(args):
os.makedirs(args.dest, exist_ok=True)
dump_file = os.path.join(args.dest, f"{args.db}.sql")
dump_db(args.host, args.user, args.password, args.db, dump_file)
archive_path = archive(dump_file, args.dest)
upload_s3(archive_path, args.bucket)
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser()
p.add_argument("--host", required=True)
p.add_argument("--user", required=True)
p.add_argument("--password", required=True)
p.add_argument("--db", required=True)
p.add_argument("--bucket", required=True)
p.add_argument("--dest", default=".")
args = p.parse_args()
main(args)
pip install boto3
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥1
ArviZ🟠 Unified InferenceData: Структурированный способ организации posteriors, предсказаний, наблюдений и логов в одном объекте.🟠 Расширенные визуализации: traceplots, diagnostics (R-hat, ESS), posterior_predictive checks через Matplotlib и Bokeh.🟠 Интеграция с популярными инструментами: поддержка PyMC, Pyro, Stan и xarray.🟠 Поддержка аналитики Bayes-моделей: summarization, posterior checks, model comparison и diagnostics удобны и понятны.🟠 Открытый проект, активно развивается, лицензия Apache 2.0.
pip install arviz
🧑💻 Пример использования:
import arviz as az
import pymc as pm
with pm.Model() as model:
α = pm.Normal("α", 0, 1)
β = pm.Normal("β", 0, 1)
σ = pm.HalfNormal("σ", 1)
μ = α + β * pm.Data("x", [1,2,3,4,5])
y = pm.Normal("y", μ, σ, observed=[1.2,1.9,2.8,4.1,4.9])
idata = pm.sample(return_inferencedata=True)
# Cводка результатов
print(az.summary(idata, var_names=["α", "β", "σ"]))
# Трассировка параметров и автокорреляции
az.plot_trace(idata, var_names=["α", "β"])
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🔥3👍2
PyPDF2, argparse, os🟠 Ищет все файлы .pdf в указанной директории🟠 Объединяет их в порядке сортировки по имени в единственный PDF🟠 Сохраняет итоговое объединение в указанное имя файла🟠 Полезен для сбора статей, отчетов, документов или презентаций
python pdf_merger.py --input-dir ./pdfs --output merged_all.pdf
import os
import argparse
from PyPDF2 import PdfMerger
def merge_pdfs(input_dir, output_file):
merger = PdfMerger()
pdf_files = sorted([
f for f in os.listdir(input_dir)
if f.lower().endswith('.pdf')
])
for pdf in pdf_files:
path = os.path.join(input_dir, pdf)
merger.append(path)
print(f"✅ Appended: {pdf}")
merger.write(output_file)
merger.close()
print(f"📄 Merged into: {output_file}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Bulk PDF Merger")
parser.add_argument('--input-dir', required=True, help='Папка с PDF')
parser.add_argument('--output', required=True, help='Имя итогового PDF')
args = parser.parse_args()
merge_pdfs(args.input_dir, args.output)
pip install PyPDF2
🟠 Компиляция серии отчетов или документов в единый PDF-файл🟠 Автоматизация сборки документации🟠 Удобное решение для подготовки больших PDF из множества файлов
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥5
Nuitka🟠 Преобразует Python-файлы в C-код, затем компилирует в нативные бинарники (используя gcc, clang или MinGW)🟠 Поддерживает большинство современных возможностей языка: аннотации типов, итераторы, вызовы, конструкции управления и пр.🟠 Оптимизации на этапе компиляции: константное сворачивание, прогноз вызова встроенных функций, вывод неизменных типов — ускорение до 1.5–2× по сравнению с CPython🟠 Можно собирать в standalone-приложения — готовые исполняемые файлы выдаются без зависимости от Python-установки у пользователя🟠 Активно развивается: последняя версия 2.6.9 вышла 28 марта 2025 года
pip install nuitka
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍7
requests, json, time, argparse🟠 Принимает адрес API, интервал опроса и файл логов🟠 Делает HTTP GET-запрос и записывает метрики ответа: timestamp, статус-код, latency, тело JSON🟠 Удобен для отслеживания изменений или ошибок в API🟠 Основан на практиках логгирования API-метрик и best practices в мониторинге REST-служб
python api_logger.py --url https://api.example.com/data --interval 60 --output api_log.jsonl
import requests, json, time, argparse
def log_response(url, interval, out_file):
with open(out_file, 'a', encoding='utf-8') as f:
while True:
try:
start = time.time()
r = requests.get(url, timeout=15)
latency = round((time.time() - start) * 1000, 2)
entry = {
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"status_code": r.status_code,
"latency_ms": latency,
"response": r.json() if 'application/json' in r.headers.get('Content-Type', '') else r.text
}
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
print(f"Logged at {entry['timestamp']}: {r.status_code}, {latency}ms")
except Exception as e:
print(f"Error fetching {url}: {e}")
time.sleep(interval)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="API Response Logger")
parser.add_argument("--url", required=True, help="API endpoint to log")
parser.add_argument("--interval", type=int, default=60, help="Polling interval in seconds")
parser.add_argument("--output", required=True, help="JSONL output file for logs")
args = parser.parse_args()
log_response(args.url, args.interval, args.output)
pip install requests
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥3😁1
Loguru🟠 Логирование "из коробки" — минимум настроек🟠 Цветной вывод, ротация файлов, асинхронность🟠 Автоматический вывод tracebacks и ошибок🟠 Удобный .add() для настройки логов
pip install loguru
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Pillow, numpy, argparse🟠 Загружает изображение любого поддерживаемого формата🟠 Конвертирует в градации серого🟠 Делит изображение на сетку и заменяет среднюю яркость каждой ячейки ASCII‑символом (@, #, . и пр.)🟠 Сохраняет ASCII-арт в файл или выводит на экран🟠 Включает CLI‑интерфейс для настройки ширины и дискреты яркости.
python ascii_art.py --input image.jpg --width 80 --output ascii.txt --detailed
import os
import argparse
from PIL import Image
import numpy as np
GSCALE_SIMPLE = "@%#*+=-:. "
GSCALE_EXTENDED = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~i!lI;:,\"^`'. "
def avg_brightness(tile):
return np.array(tile).mean()
def image_to_ascii(image_path, cols=80, more_levels=False):
image = Image.open(image_path).convert('L')
W, H = image.size
scale = 0.43 # поправка на соотношение символов в терминале
w = W / cols
h = w / scale
rows = int(H / h)
print(f"[ASCII] Рисунок: cols={cols}, rows={rows}")
ascii_img = []
gs = GSCALE_EXTENDED if more_levels else GSCALE_SIMPLE
levels = len(gs)
for row in range(rows):
y1 = int(row * h)
y2 = int((row + 1) * h) if row < rows - 1 else H
line = ""
for col in range(cols):
x1 = int(col * w)
x2 = int((col + 1) * w) if col < cols - 1 else W
tile = image.crop((x1, y1, x2, y2))
brightness = avg_brightness(tile)
idx = int(brightness * (levels - 1) / 255)
line += gs[idx]
ascii_img.append(line)
return ascii_img
def main():
parser = argparse.ArgumentParser(description="ASCII Art Image Converter")
parser.add_argument('--input', required=True, help='Путь к изображению')
parser.add_argument('--width', '-w', type=int, default=80, help='Ширина в символах')
parser.add_argument('--output', '-o', help='Выходной .txt файл (по умолчанию — stdout)')
parser.add_argument('--detailed', action='store_true',
help='Использовать мелкую градацию (GSCALE_EXTENDED)')
args = parser.parse_args()
art = image_to_ascii(args.input, cols=args.width, more_levels=args.detailed)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write('\n'.join(art))
print(f"✅ Сохранено в {args.output}")
else:
print()
print('\n'.join(art))
if __name__ == "__main__":
main()
pip install pillow numpy
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤12🔥4
Ansible Runner🟠 Позволяет интегрировать Ansible в Python-приложения🟠 Удобен для CI/CD, web-интерфейсов, API-сервисов🟠 Поддерживает запуск playbook, roles, adhoc-команд🟠 Управляет входными/выходными данными (stdout, artifacts)8
pip install ansible-runner
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍4
Скрипт, который сканирует список субдоменов, пытается определить уязвимые поддомены для takeover-а (например, Heroku, AWS S3, GitHub Pages и др.):
dns.resolver (из dnspython), requests, argparse, concurrent.futures🟠 Считывает субдомены из файла или принимает через CLI🟠 Разрешает CNAME- или A-записи DNS🟠 Если найден CNAME, сканирует по известным сигнатурам (“No such app”, “doesn’t exist”, “There isn’t a GitHub Pages site here” и др.) в HTTP-ответах🟠 Параллельно обрабатывает сотни запросов через ThreadPoolExecutor — что ускоряет проверку🟠 Помечает потенциально уязвимые хосты и сохраняет их в отчёт takeover_report.txt
python takeover_check.py --input subs.txt --dns-server 8.8.8.8 --timeout 5
#!/usr/bin/env python3
import argparse, re, requests
from dns import resolver, exception
from concurrent.futures import ThreadPoolExecutor
TAKEOVER_SIGNS = {
"heroku": re.compile(r'no such app', re.I),
"s3": re.compile(r'(NoSuchBucket|The specified bucket does not exist)', re.I),
"github": re.compile(r"There isn't a GitHub Pages site here", re.I),
"azure": re.compile(r'No DNS record found for', re.I),
}
def find_cname(host, nameserver, timeout):
res = resolver.Resolver()
res.nameservers = [nameserver]
res.timeout = timeout
res.lifetime = timeout
try:
answers = res.resolve(host, 'CNAME')
return str(answers[0].target).rstrip('.')
except (resolver.NoAnswer, resolver.NXDOMAIN):
return None
except exception.DNSException:
return None
def check_takeover(host, cname, timeout):
if not cname:
return None
url = f"http://{host}"
try:
r = requests.get(url, timeout=timeout)
html = r.text
for provider, pattern in TAKEOVER_SIGNS.items():
if pattern.search(html):
return (provider, r.status_code, len(html))
except requests.RequestException:
pass
return None
def process(host, nameserver, timeout):
cname = find_cname(host, nameserver, timeout)
suspect = check_takeover(host, cname, timeout)
return host, cname, suspect
def main(args):
heads = f"# Subdomain Takeover Scan Report: {args.input}"
print(heads)
with open(args.input) as f:
hosts = [h.strip() for h in f if h.strip()]
report = []
with ThreadPoolExecutor(max_workers=args.threads) as pool:
for host, cname, res in pool.map(lambda h: process(h, args.nameserver, args.timeout), hosts):
if res:
provider, code, size = res
msg = f"⚠️ {host} → potential {provider} takeover (cname: {cname}, code: {code}, size: {size})"
print(msg)
report.append(msg)
if report:
with open("takeover_report.txt", "w") as f:
f.write(heads + "\n\n" + "\n".join(report))
print(f"\n✅ Найдено потенциальных уязвимостей: {len(report)}. Сохранил в takeover_report.txt")
else:
print("\n✅ Уязвимых поддоменов не найдено.")
if __name__ == "__main__":
cli = argparse.ArgumentParser(description="Subdomain Takeover Checker")
cli.add_argument("--input", "-i", required=True, help="Файл со списком субдоменов, один домен на строку")
cli.add_argument("--dns-server", default="8.8.8.8", help="DNS сервер для запросов (по умолчанию: Google)")
cli.add_argument("--timeout", type=float, default=4.0, help="Таймаут DNS и HTTP")
cli.add_argument("--threads", type=int, default=50, help="Параллельность (подъем числа при больших wordlists)")
args = cli.parse_args()
main(args)
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
2❤5🔥2
Compare‑Dicts‑Lib (compare_dicts_lib)🟠 Поддержка вложенных структур, списков и match-патчинг по "id"🟠 Простое сравнение: compare_dicts(new, old) возвращает только изменения🟠 Флаг detailed=True — показывает тип изменений, старое и новое значение🟠 Параметры: ignore_keys=[…], strict_type_checking=False🟠 Можно применять diff через apply_diff(...) прямо к данным🟠 Не требует сторонних зависимостей, лицензия MIT, Python 3.7+ поддерживается
pip install compare-dicts-lib
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥4
GitPython, openai, sys, os, argparse, logging🟠 Берёт git diff --cached (или нестейдженные изменения) с помощью GitPython или repo.git в чистом виде🟠 Формирует запрос (prompt) к OpenAI: «You are an expert developer. Here is a git diff, write a concise, meaningful commit message.»🟠 Предлагает выбрать или отредактировать сгенерированный вариант🟠 При подтверждении выполняет git commit -m "..." либо передаёт через stdin git commit🟠 Работает как CLI: можно передавать флаги --no-gpt (fallback), --amend, --language=ru и т.п.🟠 Подходит для любого Git‑проекта, схема LLM‑ный генератор месседжей описана на LevelUp и Python in Plain English
# с абсолютной осторожностью: ничего не коммитите вручную — AI предложит сообщение
python ai_commit.py
import os, argparse, logging
from git import Repo
import openai
import sys
openai.api_key = os.getenv("OPENAI_API_KEY")
def get_diff(repo_path='.', staged=True):
repo = Repo(repo_path)
if staged:
return repo.git.diff('--cached', unified=3)
else:
return repo.git.diff(unified=3)
def ask_ai(diff_text, lang='en'):
prompt = (
f"You are an expert software developer.\n"
f"Provide a concise single-line commit message in {lang} for the following git diff:\n\n{diff_text}"
)
resp = openai.ChatCompletion.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.2, max_tokens=60
)
return resp.choices[0].message.content.strip()
def main():
cli = argparse.ArgumentParser(description="AI Commit Message Generator")
cli.add_argument('--no-gpt', action='store_true', help="Use default message prompt 'Update changes'")
cli.add_argument('--stage-all', action='store_true', help="Add all changes")
cli.add_argument('--language', default='en', help="Commit message language (e.g. 'ru', 'en')")
args = cli.parse_args()
repo = Repo('.')
if args.stage_all:
repo.git.add('--all')
diff = get_diff(staged=True)
if not diff.strip():
print("Нет изменений для коммита.")
sys.exit(1)
msg = "Update changes (no GPT mode)" if args.no_gpt else ask_ai(diff, lang=args.language)
print(f"\n> Suggested commit message:\n{msg}\n")
choice = input("[c]ommit / [e]dit / [s]kip? ").lower()
if choice.startswith('c'):
repo.git.commit('-m', msg)
print("✅ Committed.")
elif choice.startswith('e'):
ed = input("Input your commit message: ").strip()
if ed:
repo.git.commit('-m', ed)
print("✅ Committed with custom message.")
else:
print("✳️ Empty message. Aborting.")
else:
print("⚠️ Commit skipped.")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
pip install GitPython openai python-dotenv
(Рекомендуется: настроить OPENAI_API_KEY через .env или переменную окружения).
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍4🔥2
PhotoHolmes🟠 Готовые продвинутые методы: Adaptive CFA‑Net, CAT‑Net, FOCAL и др., на основе шумовой аномалии и локальных паттернов🟠 Модули для популярных датасетов (Columbia, CASIA, DSO‑1) — загрузка, предобработка, использование в оценке🟠 Benchmark API — сравните метод на датасете по метрикам F1‑score, IoU, MCC и др., с результатами в JSON/CSV🟠 Командный интерфейс (CLI): оценка конкретной картинки или запуск бенчмарка на папке в 1 команду🟠 Открытая, модульная архитектура: легко добавить свою логику, метрику или набор изображений для тестирования🟠 Лицензия Apache‑2.0; Python >= 3.10; активно развивается и имеет GitHub сообщество.
pip install git+https://github.com/photoholmes/photoholmes.git
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤2🥰1
requests, ctypes (Windows) или os + gsettings (Linux), argparse, schedule, platform, time🟠 Загружает фоновое изображение дня от Bing (или другого источника)🟠 Сохраняет картинку локально🟠 Устанавливает её как обои на рабочем столе🟠 Можно запускать ежедневно через cron или использовать встроенный scheduler Python
python auto_wallpaper.py --dir ~/Pictures/BingWall --daily --source bing
👨💻 Код скрипта:
import os, requests, platform, argparse, schedule, time
from datetime import datetime
def fetch_bing_wallpaper(save_dir):
data = requests.get('https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1').json()
url = 'https://www.bing.com' + data['images'][0]['url']
fname = os.path.join(save_dir, datetime.now().strftime('bing_%Y%m%d.jpg'))
resp = requests.get(url)
with open(fname, 'wb') as f: f.write(resp.content)
return fname
def set_wallpaper(path):
sys = platform.system()
if sys == 'Windows':
import ctypes
ctypes.windll.user32.SystemParametersInfoW(20, 0, path, 3)
elif sys == 'Linux':
os.system(f"gsettings set org.gnome.desktop.background picture-uri file://{path}")
# MacOS: можно через osascript — добавьте при необходимости
def job(args):
img = fetch_bing_wallpaper(args.dir)
set_wallpaper(img)
print(f"✅ Wallpaper updated: {img}")
def main():
p = argparse.ArgumentParser(description="Auto Wallpaper Changer")
p.add_argument('--dir', default=os.getcwd(), help="Папка для сохранения обоев")
p.add_argument('--daily', action='store_true', help="Менять обои каждый день")
args = p.parse_args()
os.makedirs(args.dir, exist_ok=True)
if args.daily:
schedule.every().day.at("07:00").do(job, args)
print("🚀 Wallpaper scheduler started")
while True:
schedule.run_pending()
time.sleep(60)
else:
job(args)
if __name__ == "__main__":
main()
pip install requests schedule
#скрипты
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤8🔥4
jenkinsapi 🟠 Получение статуса сборок🟠 Запуск/остановка job'ов🟠 Управление параметрами сборок🟠 Работа с логами, артефактами и queue🟠 Интеграция Jenkins в Python-скрипты
pip install jenkinsapi
#библиотеки
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3🔥2