
# Fichier: python_cheats/cheatsheets/pathlib.txt


[OK] 1. IMPORTS ET CLASSES PRINCIPALES


from pathlib import Path, PurePath, PosixPath, WindowsPath
from pathlib import PurePosixPath, PureWindowsPath

# Path: Classe principale pour chemins concrets (opérations système)
# PurePath: Classe pour chemins purs (manipulation sans accès système)
# PosixPath/WindowsPath: Classes spécifiques aux systèmes
# PurePosixPath/PureWindowsPath: Versions pures spécifiques aux systèmes



[OK] 2. CRÉATION DE PATHS


# Création basique
p = Path('folder/file.txt')                 # Chemin relatif
p = Path('/absolute/path/file.txt')         # Chemin absolu
p = Path('.')                               # Répertoire courant
p = Path('..')                              # Répertoire parent

# Chemins spéciaux
home = Path.home()                          # Répertoire utilisateur (~)
cwd = Path.cwd()                            # Répertoire de travail actuel

# À partir de chaînes multiples
p = Path('folder', 'subfolder', 'file.txt') # Arguments multiples
p = Path(*['a', 'b', 'c'])                  # Unpacking de liste

# Pure paths (sans accès système)
pp = PurePath('folder/file.txt')            # Pour manipulation pure
ppp = PurePosixPath('/unix/path')           # Force style Unix
pwp = PureWindowsPath('C:\\Windows\\path')  # Force style Windows



[OK] 3. JOINTURES ET CONSTRUCTION DE CHEMINS


# Opérateur / (recommandé)
base = Path('project')
full = base / 'src' / 'main.py'             # project/src/main.py

# Jointure multiple
parts = ['docs', 'api', 'index.html']
path = Path('project').joinpath(*parts)     # project/docs/api/index.html

# Avec variables
folder = 'data'
filename = 'file.csv'
p = Path(folder) / filename                 # data/file.csv



[OK] 4. PROPRIÉTÉS DES CHEMINS


p = Path('/home/user/documents/report.final.pdf')

# Nom et extensions
p.name                                      # 'report.final.pdf' (nom complet)
p.stem                                      # 'report.final' (sans dernière extension)
p.suffix                                    # '.pdf' (dernière extension)
p.suffixes                                  # ['.final', '.pdf'] (toutes les extensions)

# Parties du chemin
p.parent                                    # Path('/home/user/documents')
p.parents[0]                                # Path('/home/user/documents')
p.parents[1]                                # Path('/home/user')
p.parents[2]                                # Path('/home')
p.anchor                                    # '/' (racine Unix) ou 'C:\\' (Windows)
p.parts                                     # ('/', 'home', 'user', 'documents', 'report.final.pdf')

# Informations système
p.drive                                     # '' (Unix) ou 'C:' (Windows)
p.root                                      # '/' (Unix) ou '\\' (Windows)

# Conversion en chaîne
str(p)                                      # '/home/user/documents/report.final.pdf'
p.as_posix()                                # Toujours avec / (même Windows)
p.as_uri()                                  # 'file:///home/user/documents/report.final.pdf'



[OK] 5. VÉRIFICATIONS ET TESTS


p = Path('myfile.txt')

# Existence
p.exists()                                  # True si existe (fichier ou dossier)
p.is_file()                                 # True si fichier
p.is_dir()                                  # True si répertoire
p.is_symlink()                              # True si lien symbolique
p.is_mount()                                # True si point de montage
p.is_socket()                               # True si socket Unix
p.is_fifo()                                 # True si FIFO (named pipe)
p.is_block_device()                         # True si périphérique bloc
p.is_char_device()                          # True si périphérique caractère

# Type de chemin
p.is_absolute()                             # True si chemin absolu
p.is_relative_to('/home')                   # True si relatif à /home (Python 3.9+)
p.is_reserved()                             # True si nom réservé Windows (CON, PRN, etc.)



[OK] 6. LECTURE ET ÉCRITURE DE FICHIERS


p = Path('data.txt')

# LECTURE
# -------
# Lire tout le contenu texte
content = p.read_text(encoding='utf-8')     # Retourne string

# Lire contenu binaire
data = p.read_bytes()                       # Retourne bytes

# ÉCRITURE
# --------
# Écrire texte (écrase si existe)
p.write_text('Hello World\n', encoding='utf-8')

# Écrire binaire
p.write_bytes(b'\x00\x01\x02\x03')

# OPEN (pour opérations avancées)
# --------------------------------
# Mode lecture
with p.open('r', encoding='utf-8') as f:
    lines = f.readlines()

# Mode écriture
with p.open('w', encoding='utf-8') as f:
    f.write('Hello\n')

# Mode ajout
with p.open('a', encoding='utf-8') as f:
    f.write('New line\n')

# Mode binaire
with p.open('rb') as f:
    data = f.read()



[OK] 7. CRÉATION ET SUPPRESSION


# CRÉER RÉPERTOIRES
# -----------------
p = Path('new_folder')
p.mkdir()                                   # Créer (erreur si parent n'existe pas)
p.mkdir(parents=True)                       # Créer avec tous les parents
p.mkdir(exist_ok=True)                      # Ne pas lever erreur si existe
p.mkdir(mode=0o755)                         # Avec permissions spécifiques

# CRÉER FICHIER VIDE
# -------------------
p = Path('empty.txt')
p.touch()                                   # Créer fichier vide ou MAJ timestamp
p.touch(exist_ok=True)                      # Ne pas lever erreur si existe
p.touch(mode=0o644)                         # Avec permissions

# SUPPRIMER FICHIERS
# ------------------
p.unlink()                                  # Supprimer fichier (erreur si absent)
p.unlink(missing_ok=True)                   # Pas d'erreur si n'existe pas

# SUPPRIMER RÉPERTOIRES
# ---------------------
p.rmdir()                                   # Supprimer répertoire VIDE uniquement

# Pour supprimer récursivement, utiliser shutil
import shutil
shutil.rmtree(p)                            # Supprime tout le contenu



[OK] 8. RENOMMER, DÉPLACER, COPIER


p = Path('old_name.txt')

# RENOMMER dans le même répertoire
new_p = p.rename('new_name.txt')            # Retourne nouveau Path
# Erreur si destination existe déjà

# REMPLACER (écrase si existe)
new_p = p.replace('other_name.txt')         # Force le remplacement

# DÉPLACER vers autre dossier
new_p = p.rename('other_folder/file.txt')

# COPIER (nécessite shutil)
import shutil
shutil.copy(p, 'destination.txt')           # Copie fichier
shutil.copy2(p, 'dest.txt')                 # Copie avec métadonnées
shutil.copytree(p, 'dest_folder')           # Copie répertoire récursif



[OK] 9. LISTING ET PARCOURS DE RÉPERTOIRES


p = Path('.')

# ITERDIR: Lister contenu direct (non récursif)
# ----------------------------------------------
for item in p.iterdir():
    if item.is_file():
        print(f"Fichier: {item}")
    elif item.is_dir():
        print(f"Dossier: {item}")

# GLOB: Recherche avec patterns
# ------------------------------
# Fichiers .txt dans dossier courant
for txt_file in p.glob('*.txt'):
    print(txt_file)

# Fichiers .py dans sous-dossiers (un niveau)
for py_file in p.glob('*/*.py'):
    print(py_file)

# Recherche récursive avec **
for py_file in p.glob('**/*.py'):
    print(py_file)

# RGLOB: Glob récursif automatique
# ---------------------------------
# Équivalent à glob('**/*.py')
for py_file in p.rglob('*.py'):
    print(py_file)

# Patterns multiples
for file in p.rglob('*.{txt,md}'):          # Ne fonctionne pas directement
    print(file)

# Pour patterns multiples, utiliser:
import fnmatch
for file in p.rglob('*'):
    if fnmatch.fnmatch(file.name, '*.txt') or fnmatch.fnmatch(file.name, '*.md'):
        print(file)

# WALK: Parcours récursif complet (Python 3.12+)
# -----------------------------------------------
for root, dirs, files in p.walk():
    print(f"Dans {root}:")
    for file in files:
        print(f"  - {file}")



[OK] 10. STATISTIQUES ET MÉTADONNÉES


p = Path('myfile.txt')

# Obtenir objet stat
stat = p.stat()

# Tailles
stat.st_size                                # Taille en octets

# Timestamps (en secondes depuis epoch)
stat.st_mtime                               # Dernière modification
stat.st_atime                               # Dernier accès
stat.st_ctime                               # Création (Windows) / changement métadonnées (Unix)

# Conversion en datetime
from datetime import datetime
mtime = datetime.fromtimestamp(stat.st_mtime)

# Permissions (Unix)
stat.st_mode                                # Mode fichier
oct(stat.st_mode)                           # Mode en octal (ex: '0o100644')

# Propriétaire (Unix)
stat.st_uid                                 # User ID
stat.st_gid                                 # Group ID

# Obtenir propriétaire/groupe (nécessite pwd/grp sur Unix)
import pwd, grp
owner = pwd.getpwuid(stat.st_uid).pw_name
group = grp.getgrgid(stat.st_gid).gr_name

# Liens (Unix)
stat.st_nlink                               # Nombre de hard links

# Inodes (Unix)
stat.st_ino                                 # Numéro inode
stat.st_dev                                 # Device ID



[OK] 11. PERMISSIONS ET PROPRIÉTÉS (UNIX)


p = Path('myfile.txt')

# LIRE PERMISSIONS
# ----------------
import os
mode = p.stat().st_mode
readable = bool(mode & os.R_OK)
writable = bool(mode & os.W_OK)
executable = bool(mode & os.X_OK)

# MODIFIER PERMISSIONS
# --------------------
p.chmod(0o644)                              # rw-r--r--
p.chmod(0o755)                              # rwxr-xr-x

# Ajouter permission
current = p.stat().st_mode
p.chmod(current | 0o111)                    # Ajoute exécution pour tous

# PROPRIÉTAIRE (nécessite droits root généralement)
# --------------------------------------------------
p.owner()                                   # Nom du propriétaire (Python 3.9+)
p.group()                                   # Nom du groupe (Python 3.9+)

# Changer propriétaire (nécessite os module)
import os
os.chown(p, uid=1000, gid=1000)



[OK] 12. CHEMINS ABSOLUS, RELATIFS ET RÉSOLUTION


p = Path('folder/file.txt')

# CONVERSION ABSOLUE
# ------------------
abs_p = p.absolute()                        # Chemin absolu (peut contenir .. ou .)
resolved = p.resolve()                      # Chemin absolu résolu (canonique)
resolved_strict = p.resolve(strict=True)    # Erreur si n'existe pas

# CHEMINS RELATIFS
# ----------------
full = Path('/home/user/documents/file.txt')
base = Path('/home/user')
relative = full.relative_to(base)           # documents/file.txt

# Relatif avec wildcard (Python 3.12+)
relative = full.relative_to('/home/*/documents')

# COMPARAISON DE CHEMINS
# ----------------------
p1 = Path('folder/./file.txt')
p2 = Path('folder/file.txt')
p1.resolve() == p2.resolve()                # True (même chemin canonique)

# Vérifier si sous-chemin
child = Path('/home/user/docs/file.txt')
parent = Path('/home/user')
child.is_relative_to(parent)                # True (Python 3.9+)



[OK] 13. MANIPULATION DE NOMS ET EXTENSIONS


p = Path('folder/document.backup.txt')

# CHANGER EXTENSION
# -----------------
new_p = p.with_suffix('.md')                # folder/document.backup.md
no_ext = p.with_suffix('')                  # folder/document.backup

# CHANGER NOM (sans extension)
# ----------------------------
new_p = p.with_stem('report')               # folder/report.backup.txt (Python 3.9+)

# CHANGER NOM COMPLET
# -------------------
new_p = p.with_name('newfile.pdf')          # folder/newfile.pdf

# MANIPULATIONS COMPLEXES
# -----------------------
# Retirer toutes les extensions
def remove_all_suffixes(path):
    while path.suffix:
        path = path.with_suffix('')
    return path

p = Path('file.tar.gz')
base = remove_all_suffixes(p)               # file

# Changer extension multiple
p = Path('data.csv')
new_p = p.with_suffix('.tar').with_suffix('.tar.gz')  # Ne marche pas
# Meilleure approche:
new_p = p.with_name(p.stem + '.tar.gz')     # data.tar.gz



[OK] 14. LIENS SYMBOLIQUES


# CRÉER LIEN SYMBOLIQUE
# ---------------------
target = Path('original.txt')
link = Path('link.txt')
link.symlink_to(target)                     # Lien vers fichier
link.symlink_to(target, target_is_directory=False)

# Lien vers répertoire
dir_link = Path('link_dir')
dir_link.symlink_to('original_dir', target_is_directory=True)

# CRÉER HARD LINK
# ---------------
original = Path('file.txt')
hardlink = Path('hardlink.txt')
hardlink.hardlink_to(original)              # Python 3.10+

# LIRE CIBLE DU LIEN
# ------------------
link = Path('mylink')
if link.is_symlink():
    target = link.readlink()                # Retourne Path de la cible
    print(f"Pointe vers: {target}")

# RÉSOLUTION
# ----------
link.resolve()                              # Suit les liens et retourne cible



[OK] 15. CORRESPONDANCE DE PATTERNS


p = Path('documents/report.pdf')

# MATCH: Tester si chemin correspond à pattern
# ---------------------------------------------
p.match('*.pdf')                            # True (match nom uniquement)
p.match('**/report.pdf')                    # True (match chemin complet)
p.match('documents/*.pdf')                  # True
p.match('*/report.*')                       # True

# Sensibilité à la casse (dépend du système)
p.match('REPORT.PDF')                       # False sur Unix, True sur Windows

# FULL_MATCH: Match complet du chemin (Python 3.13+)
# ---------------------------------------------------
p.full_match('documents/report.pdf')        # True
p.full_match('*/report.pdf')                # True
p.full_match('report.pdf')                  # False (chemin incomplet)



[OK] 16. COMPARAISON ET OPÉRATIONS


p1 = Path('folder/file.txt')
p2 = Path('folder/file.txt')
p3 = Path('other/file.txt')

# ÉGALITÉ
# -------
p1 == p2                                    # True (même chemin)
p1 == p3                                    # False

# COMPARAISON LEXICOGRAPHIQUE
# ---------------------------
p1 < p3                                     # True ('folder' < 'other')
p1 <= p2                                    # True
p3 > p1                                     # True

# HASH (utilisable dans sets/dicts)
# ---------------------------------
path_set = {Path('a'), Path('b'), Path('a')}  # {Path('a'), Path('b')}
path_dict = {Path('key'): 'value'}

# SAMEFILE: Tester si même fichier physique
# ------------------------------------------
p1 = Path('file.txt')
p2 = Path('link_to_file.txt')  # Lien symbolique
if p1.exists() and p2.exists():
    p1.samefile(p2)                         # True si même inode



[OK] 17. OPÉRATIONS AVANCÉES


# EXPANDUSER: Développer ~ en home
# --------------------------------
p = Path('~/documents/file.txt')
expanded = p.expanduser()                   # /home/username/documents/file.txt

# VARIABLES D'ENVIRONNEMENT (nécessite os.path.expandvars)
# ---------------------------------------------------------
import os
p = Path(os.path.expandvars('$HOME/documents'))

# OBTENIR CHEMIN TEMPORAIRE
# -------------------------
import tempfile
temp_dir = Path(tempfile.gettempdir())      # /tmp sur Unix
temp_file = Path(tempfile.mktemp())         # ATTENTION: non sécurisé

# Utiliser TemporaryDirectory (recommandé)
with tempfile.TemporaryDirectory() as tmpdir:
    temp_path = Path(tmpdir) / 'file.txt'
    temp_path.write_text('data')

# OBTENIR TAILLE RÉCURSIVE D'UN DOSSIER
# --------------------------------------
def get_dir_size(path):
    """Calcule taille totale d'un répertoire"""
    return sum(f.stat().st_size for f in path.rglob('*') if f.is_file())

size = get_dir_size(Path('.'))
print(f"Taille: {size / (1024*1024):.2f} MB")

# COMPTER FICHIERS PAR TYPE
# -------------------------
from collections import Counter
extensions = Counter(p.suffix for p in Path('.').rglob('*') if p.is_file())
# Counter({'.py': 15, '.txt': 8, '.md': 3})



[OK] 18. GESTION D'ERREURS COURANTES


from pathlib import Path
import errno

p = Path('myfile.txt')

# FileNotFoundError
try:
    content = p.read_text()
except FileNotFoundError:
    print("Fichier introuvable")

# PermissionError
try:
    p.write_text('data')
except PermissionError:
    print("Pas de permission d'écriture")

# FileExistsError
try:
    p.mkdir()
except FileExistsError:
    print("Répertoire existe déjà")
    # Ou utiliser exist_ok=True

# IsADirectoryError
try:
    Path('folder').read_text()
except IsADirectoryError:
    print("C'est un répertoire, pas un fichier")

# NotADirectoryError
try:
    Path('file.txt').iterdir()
except NotADirectoryError:
    print("C'est un fichier, pas un répertoire")

# OSError avec errno
try:
    p.symlink_to('target')
except OSError as e:
    if e.errno == errno.EEXIST:
        print("Le lien existe déjà")
    elif e.errno == errno.EACCES:
        print("Permission refusée")



[OK] 19. BONNES PRATIQUES ET PATTERNS


# VÉRIFIER EXISTENCE AVANT OPÉRATION
# -----------------------------------
if p.exists():
    content = p.read_text()
else:
    p.write_text('default content')

# CRÉER STRUCTURE DE PROJET
# --------------------------
def create_project_structure(name):
    """Crée structure complète d'un projet"""
    root = Path(name)
    
    # Créer dossiers
    (root / 'src').mkdir(parents=True, exist_ok=True)
    (root / 'tests').mkdir(exist_ok=True)
    (root / 'docs').mkdir(exist_ok=True)
    (root / 'data').mkdir(exist_ok=True)
    
    # Créer fichiers
    (root / 'README.md').touch()
    (root / 'requirements.txt').touch()
    (root / '.gitignore').write_text('__pycache__/\n*.pyc\n')
    (root / 'src' / '__init__.py').touch()
    
    return root

# SAUVEGARDER AVEC BACKUP
# ------------------------
def safe_write(path, content):
    """Écrit avec backup de l'ancien fichier"""
    path = Path(path)
    if path.exists():
        backup = path.with_suffix(path.suffix + '.bak')
        path.rename(backup)
    path.write_text(content)

# TROUVER FICHIERS MODIFIÉS RÉCEMMENT
# ------------------------------------
from datetime import datetime, timedelta

def find_recent_files(directory, days=7):
    """Trouve fichiers modifiés dans les N derniers jours"""
    cutoff = datetime.now().timestamp() - (days * 86400)
    recent = []
    
    for f in Path(directory).rglob('*'):
        if f.is_file() and f.stat().st_mtime > cutoff:
            recent.append(f)
    
    return recent

# NETTOYAGE DE FICHIERS TEMPORAIRES
# ----------------------------------
def cleanup_temp_files(directory, pattern='*.tmp'):
    """Supprime fichiers temporaires"""
    deleted = 0
    for tmp in Path(directory).rglob(pattern):
        if tmp.is_file():
            tmp.unlink()
            deleted += 1
    return deleted



[OK] 20. DIFFÉRENCES AVEC OS.PATH


# Pathlib (moderne, orienté objet)    vs    os.path (ancien, fonctionnel)
# ================================          =============================

# Construction de chemins
Path('a') / 'b' / 'c'                 # vs  os.path.join('a', 'b', 'c')

# Nom du fichier
p.name                                # vs  os.path.basename(p)

# Extension
p.suffix                              # vs  os.path.splitext(p)[1]

# Nom sans extension
p.stem                                # vs  os.path.splitext(os.path.basename(p))[0]

# Répertoire parent
p.parent                              # vs  os.path.dirname(p)

# Existe?
p.exists()                            # vs  os.path.exists(p)

# Est fichier?
p.is_file()                           # vs  os.path.isfile(p)

# Chemin absolu
p.absolute()                          # vs  os.path.abspath(p)

# Lire fichier
p.read_text()                         # vs  open(p).read()

# Glob
p.glob('*.txt')                       # vs  glob.glob(str(p / '*.txt'))



# CONCLUSION


# Pathlib est le standard moderne pour manipuler chemins en Python
# Avantages:
#   - Orienté objet et chainable
#   - Plus lisible et intuitif
#   - Méthodes intégrées pour opérations courantes
#   - Compatible multi-plateforme
#   - Type-safe (IDE autocompletion)
#
# Utiliser pathlib pour nouveau code
# Migrer progressivement ancien code utilisant os.path