AI Agents · Lekcja

Bezpieczne operacje na plikach z obsługą błędów

Sprawdzanie istnienia plików i uprawnień oraz łagodne obsługiwanie błędów wejścia-wyjścia.

Lekcja 4 z 413 kroki

Bezpieczne operacje na plikach z obsługą błędów to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Dlaczego bezpieczne operacje na plikach mają znaczenie

Operacje na plikach wykonywane przez agenty mogą zakończyć się niepowodzeniem na wiele sposobów: plik może nie istnieć, agent może nie mieć odpowiednich uprawnień, ścieżka może wskazywać katalog albo w trakcie zapisu może zabraknąć miejsca na dysku. Agent, który ulega awarii z powodu błędu pliku, pozostawia częściowe dane wyjściowe i uszkodzony stan. Programowanie defensywne z właściwymi kontrolami i obsługą błędów zwiększa odporność agentów.

from pathlib import Path

# Unsafe: crashes with FileNotFoundError
# content = Path('missing.txt').read_text()

# Safe: check first
path = Path('config.json')
if path.exists():
    content = path.read_text(encoding='utf-8')
    print('Loaded config')
else:
    print(f'Config not found at {path.resolve()}')
    content = '{}'  # use default

Path.exists() i Path.is_file()

Przed odczytaniem pliku należy sprawdzić, czy istnieje i czy rzeczywiście jest zwykłym plikiem (a nie katalogiem, dowiązaniem symbolicznym do katalogu ani plikiem specjalnym). Path.exists() zwraca True dla dowolnego obiektu systemu plików, natomiast Path.is_file() zwraca True tylko dla zwykłych plików.

from pathlib import Path

path = Path('data/report.csv')

# Chain of checks
if not path.exists():
    print(f'Not found: {path}')
elif not path.is_file():
    print(f'Not a regular file: {path} (is_dir={path.is_dir()})')
elif path.stat().st_size == 0:
    print(f'File is empty: {path}')
else:
    # Safe to read
    import csv
    with open(path, 'r', encoding='utf-8', newline='') as f:
        reader = csv.DictReader(f)
        rows = list(reader)
    print(f'Read {len(rows)} rows')

os.access() — sprawdzanie uprawnień

os.access(path, mode) sprawdza, czy bieżący proces ma określone uprawnienie do pliku. Do odczytu należy użyć os.R_OK, do zapisu — os.W_OK, a do wykonywania — os.X_OK. Jest to przydatne przed wykonaniem operacji wymagających określonych uprawnień.

import os
from pathlib import Path

def check_file_access(path):
    p = Path(path)

    checks = {
        'exists': p.exists(),
        'is_file': p.is_file(),
        'readable': os.access(p, os.R_OK),
        'writable': os.access(p, os.W_OK),
    }

    for check, result in checks.items():
        status = 'OK' if result else 'FAIL'
        print(f'  {check}: {status}')

    return all(checks.values())

if check_file_access('data/input.json'):
    print('File is accessible')
else:
    print('Access problem — check path and permissions')

Obsługa wyjątku FileNotFoundError

Wyjątek FileNotFoundError (podklasa OSError) jest zgłaszany podczas próby otwarcia nieistniejącego pliku. Należy przechwytywać go jawnie i wyświetlać przydatny komunikat o błędzie zawierający oczekiwaną ścieżkę, aby użytkownik lub operator dokładnie wiedział, czego brakuje.

import json
from pathlib import Path

def load_agent_config(config_path='agent_config.json'):
    path = Path(config_path)
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f'Config file not found: {path.resolve()}')
        print('Create agent_config.json with your settings')
        print('Example: {"model": "gpt-4o", "max_retries": 3}')
        return {}  # return empty config as default
    except json.JSONDecodeError as e:
        print(f'Invalid JSON in {path}: {e}')
        return {}

# --- demo ---
config = load_agent_config('does_not_exist_config.json')
print(f'Config used: {config}')

Obsługa wyjątku PermissionError

Wyjątek PermissionError jest zgłaszany, gdy proces nie ma uprawnień do odczytu lub zapisu pliku. Może się tak zdarzyć w przypadku plików systemowych, plików należących do innego użytkownika lub plików z ograniczonymi uprawnieniami. Należy zawsze przechwytywać go osobno, a nie razem z FileNotFoundError — te wyjątki wymagają różnych reakcji.

from pathlib import Path

def read_file_safely(path):
    try:
        return Path(path).read_text(encoding='utf-8')
    except FileNotFoundError:
        print(f'File not found: {path}')
        return None
    except PermissionError:
        import os
        print(f'Permission denied: {path}')
        print(f'File permissions: {oct(Path(path).stat().st_mode)}')
        print(f'Current user: {os.getlogin()}')
        print('Try: chmod +r ' + str(path))
        return None
    except IsADirectoryError:
        print(f'Path is a directory, not a file: {path}')
        return None

# --- demo ---
import os
print(read_file_safely('does_not_exist.txt'))

os.makedirs('a_directory', exist_ok=True)
print(read_file_safely('a_directory'))

Obsługa wyjątku IsADirectoryError

Wyjątek IsADirectoryError jest zgłaszany podczas próby otwarcia katalogu tak, jakby był plikiem. Może się tak zdarzyć, gdy agent nieprawidłowo konstruuje ścieżkę — na przykład dołącza nazwę pliku, która już istnieje jako katalog. Należy zawsze przechwytywać ten wyjątek, aby wyświetlać zrozumiałe informacje o błędzie.

from pathlib import Path

def safe_write(output_path, content):
    path = Path(output_path)

    # Check the path is not an existing directory
    if path.is_dir():
        raise IsADirectoryError(
            f'Cannot write file: {path} is a directory. '
            f'Use a filename like {path}/output.txt instead.'
        )

    # Ensure parent directory exists
    path.parent.mkdir(parents=True, exist_ok=True)

    try:
        path.write_text(content, encoding='utf-8')
        print(f'Written: {path} ({len(content)} chars)')
    except IsADirectoryError as e:
        print(f'Path error: {e}')
    except PermissionError:
        print(f'Cannot write to {path} — permission denied')

# --- demo ---
import os
safe_write('demo_output/report.txt', 'Agent finished the task.')

os.makedirs('already_a_dir', exist_ok=True)
try:
    safe_write('already_a_dir', 'this will fail')
except IsADirectoryError as e:
    print(f'Rejected: {e}')

Atomowe zapisy z użyciem tempfile

Bezpośredni zapis do pliku jest niebezpieczny — jeśli agent ulegnie awarii w trakcie zapisu, plik pozostanie częściowo zapisany i uszkodzony. Rozwiązaniem jest atomowy zapis: najpierw należy zapisać dane do pliku tymczasowego, a następnie zmienić jego nazwę na docelową ścieżkę. Operacja zmiany nazwy jest atomowa w systemach POSIX — plik docelowy zawiera więc albo starą wersję, albo nową, nigdy zaś niepełną.

import tempfile
import os
import json
from pathlib import Path

def atomic_write_json(file_path, data):
    path = Path(file_path)
    path.parent.mkdir(parents=True, exist_ok=True)

    # Write to temp file in same directory
    tmp_fd, tmp_path = tempfile.mkstemp(
        dir=path.parent,
        prefix='.tmp_',
        suffix='.json'
    )
    try:
        with os.fdopen(tmp_fd, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        # Atomic rename: replaces final file in one operation
        os.replace(tmp_path, path)
        print(f'Atomically wrote: {path}')
    except Exception as e:
        os.unlink(tmp_path)  # clean up temp file on error
        raise

# --- demo ---
atomic_write_json('demo_state/state.json', {'step': 3, 'status': 'running'})
print('File contents:', Path('demo_state/state.json').read_text(encoding='utf-8'))

Blokady plików dla współbieżnych agentów

Gdy wiele instancji agenta działa równolegle i zapisuje dane do tego samego pliku, warunki wyścigu prowadzą do uszkodzenia danych. Należy używać blokad plików za pomocą modułu fcntl (Linux/macOS) lub wieloplatformowej biblioteki filelock, aby zapobiegać równoczesnym zapisom.

from filelock import FileLock, Timeout
import json
from pathlib import Path

COUNTER_FILE = Path('shared_counter.json')
LOCK_FILE = Path('shared_counter.json.lock')

def increment_counter():
    lock = FileLock(str(LOCK_FILE), timeout=10)
    try:
        with lock:
            # Only one process can be here at a time
            if COUNTER_FILE.exists():
                data = json.loads(COUNTER_FILE.read_text())
            else:
                data = {'count': 0}

            data['count'] += 1
            COUNTER_FILE.write_text(
                json.dumps(data, indent=2)
            )
            return data['count']
    except Timeout:
        print('Could not acquire lock within 10 seconds')
        return None

Bezpieczne usuwanie ze sprawdzaniem istnienia

Usunięcie nieistniejącego pliku powoduje zgłoszenie wyjątku FileNotFoundError. Usunięcie katalogu za pomocą Path.unlink() powoduje zgłoszenie wyjątku IsADirectoryError. W celu bezpiecznego usuwania należy użyć Path.unlink(missing_ok=True) (Python 3.8+) albo najpierw sprawdzić istnienie pliku.

from pathlib import Path
import shutil

# Safe file deletion (Python 3.8+)
Path('temp_output.json').unlink(missing_ok=True)

# Safe directory deletion
def safe_remove(path):
    p = Path(path)
    if not p.exists():
        print(f'Already gone: {p}')
        return
    if p.is_file():
        p.unlink()
        print(f'Deleted file: {p}')
    elif p.is_dir():
        shutil.rmtree(p)
        print(f'Deleted directory: {p}')
    else:
        print(f'Unknown file type: {p}')

# Clean up temporary workspace
safe_remove('/tmp/agent_workspace/run_001')

Sprawdzanie miejsca na dysku przed zapisem

Agenty zapisujące duże pliki (wyniki działania AI, zbiory danych, dzienniki) powinny najpierw sprawdzić dostępne miejsce na dysku. shutil.disk_usage(path) zwraca całkowitą, zajętą i wolną liczbę bajtów. Przed rozpoczęciem długiej operacji zapisu należy sprawdzić, czy wolne miejsce przekracza oczekiwany rozmiar wyniku.

import shutil
from pathlib import Path

def check_disk_space(output_dir, required_bytes):
    path = Path(output_dir)
    path.mkdir(parents=True, exist_ok=True)

    usage = shutil.disk_usage(path)
    free_gb = usage.free / (1024 ** 3)
    required_gb = required_bytes / (1024 ** 3)

    print(f'Disk free: {free_gb:.2f} GB')
    print(f'Required:  {required_gb:.2f} GB')

    if usage.free < required_bytes * 1.1:  # 10% safety margin
        raise IOError(
            f'Insufficient disk space: '
            f'{free_gb:.2f} GB free, '
            f'{required_gb:.2f} GB required'
        )
    return True

# Before writing a 500 MB dataset
check_disk_space('/tmp/output', 500 * 1024 * 1024)

Tworzenie kopii zapasowej przed nadpisaniem

Gdy agent aktualizuje istniejący plik, warto zachować kopię zapasową poprzedniej wersji. Umożliwia to wycofanie zmian, jeśli nowa zawartość jest nieprawidłowa. Należy używać nazwy kopii zapasowej zawierającej znacznik czasu oraz ograniczyć liczbę kopii, aby nie zapełnić miejsca na dysku.

import shutil
import datetime
from pathlib import Path

def write_with_backup(file_path, content, max_backups=5):
    path = Path(file_path)
    backup_dir = path.parent / '.backups'
    backup_dir.mkdir(exist_ok=True)

    # Backup existing file
    if path.exists():
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        backup = backup_dir / f'{path.name}.{timestamp}'
        shutil.copy2(path, backup)
        print(f'Backed up to: {backup}')

    # Write new content
    path.write_text(content, encoding='utf-8')

    # Prune old backups
    backups = sorted(backup_dir.glob(f'{path.name}.*'))
    for old in backups[:-max_backups]:
        old.unlink()
        print(f'Pruned old backup: {old.name}')

# --- demo ---
write_with_backup('demo_notes.txt', 'version 1')
write_with_backup('demo_notes.txt', 'version 2')
print('Backups:', [p.name for p in sorted(Path('.backups').glob('demo_notes.txt.*'))])

Szybki test: atomowe zapisy

Sprawdź swoją wiedzę na temat bezpiecznych wzorców zapisu do plików.

Podsumowanie bezpiecznych operacji na plikach

Agenty potrafią teraz bezpiecznie obsługiwać pliki:

  • Sprawdzanie przed uzyskaniem dostępu: Path.exists() + Path.is_file() + os.access(path, os.R_OK)
  • Należy przechwytywać wyjątki FileNotFoundError, PermissionError i IsADirectoryError, wyświetlając jasne komunikaty
  • Należy używać atomowych zapisów (tempfile + os.replace), aby zapobiegać powstawaniu częściowych lub uszkodzonych plików po awarii
  • W przypadku zapisu do tego samego pliku przez wiele agentów należy używać blokad plików (biblioteka filelock)
  • Należy używać missing_ok=True, aby bezpiecznie usuwać pliki bez powodowania wyjątku FileNotFoundError
  • Przed dużymi zapisami należy sprawdzać miejsce na dysku, a przed nadpisaniem ważnych plików tworzyć kopie zapasowe
Bezpłatny start

Ucz się AI Agents dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
60
Lekcje
239

Często zadawane pytania

Czy lekcja „Bezpieczne operacje na plikach z obsługą błędów” jest bezpłatna?

Tak — pełny tekst „Bezpieczne operacje na plikach z obsługą błędów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Bezpieczne operacje na plikach z obsługą błędów”?

Sprawdzanie istnienia plików i uprawnień oraz łagodne obsługiwanie błędów wejścia-wyjścia. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Bezpieczne operacje na plikach z obsługą błędów”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Odczyt i zapis plików w kontekście agenta
  2. Przechodzenie po katalogach i wyszukiwanie plików
  3. Obsługa formatów plików: CSV, JSON i TXT
  4. Bezpieczne operacje na plikach z obsługą błędów
← Powrót do AI Agents