or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

build-backend.mdbuilders.mdconfiguration.mdconstraints.mdfactory-core.mdindex.mdjson-validation.mdpackages.mdutilities.mdvcs-support.mdversion-system.md

utilities.mddocs/

0

# Utilities and Validation

1

2

Poetry Core provides various utility functions for JSON schema validation, SPDX license handling, file type detection, and other helper functions that support the core functionality.

3

4

## Core Imports

5

6

```python

7

# JSON validation

8

from poetry.core.json import validate_object, ValidationError

9

10

# SPDX license support

11

from poetry.core.spdx.license import License

12

from poetry.core.spdx.helpers import license_by_id

13

14

# Helper utilities

15

from poetry.core.utils.helpers import (

16

combine_unicode,

17

readme_content_type,

18

temporary_directory

19

)

20

21

# Version helpers

22

from poetry.core.version.helpers import format_python_constraint, PYTHON_VERSION

23

24

# VCS support

25

from poetry.core.vcs.git import Git, GitConfig, ParsedUrl, GitError

26

27

# Compatibility utilities

28

from poetry.core.utils._compat import WINDOWS, tomllib

29

``` { .api }

30

31

## JSON Schema Validation

32

33

### validate_object

34

35

```python

36

def validate_object(obj: dict[str, Any], schema_name: str) -> list[str]:

37

"""

38

Validate object against JSON schema.

39

40

Args:

41

obj: Dictionary object to validate

42

schema_name: Name of schema to validate against

43

44

Returns:

45

List of validation error messages (empty if valid)

46

47

Raises:

48

ValidationError: If validation fails with detailed error information

49

50

Available Schemas:

51

- "poetry-schema": Main Poetry configuration schema

52

- "poetry-plugins-schema": Poetry plugins configuration

53

54

Example:

55

>>> config = {"name": "my-package", "version": "1.0.0"}

56

>>> errors = validate_object(config, "poetry-schema")

57

>>> if errors:

58

... print("Validation errors:", errors)

59

... else:

60

... print("Configuration is valid")

61

"""

62

``` { .api }

63

64

### ValidationError

65

66

```python

67

class ValidationError(ValueError):

68

"""

69

JSON schema validation error.

70

71

Raised when object validation against schema fails,

72

providing detailed information about validation issues.

73

"""

74

75

def __init__(self, message: str, errors: list[str] | None = None) -> None:

76

"""

77

Create validation error.

78

79

Args:

80

message: Error message

81

errors: List of specific validation errors

82

"""

83

84

@property

85

def errors(self) -> list[str]:

86

"""List of specific validation errors."""

87

``` { .api }

88

89

## SPDX License Support

90

91

### License

92

93

```python

94

class License:

95

"""

96

SPDX license information.

97

98

Named tuple containing license metadata from SPDX license list.

99

"""

100

101

id: str # SPDX license identifier (e.g., "MIT", "Apache-2.0")

102

name: str # Full license name

103

is_osi_approved: bool # Whether OSI approved

104

is_deprecated: bool # Whether license is deprecated

105

106

def __init__(

107

self,

108

id: str,

109

name: str,

110

is_osi_approved: bool = False,

111

is_deprecated: bool = False

112

) -> None:

113

"""

114

Create license information.

115

116

Args:

117

id: SPDX license identifier

118

name: Full license name

119

is_osi_approved: Whether license is OSI approved

120

is_deprecated: Whether license is deprecated

121

122

Example:

123

>>> license = License(

124

... id="MIT",

125

... name="MIT License",

126

... is_osi_approved=True

127

... )

128

"""

129

``` { .api }

130

131

### license_by_id

132

133

```python

134

def license_by_id(license_id: str) -> License:

135

"""

136

Get license information by SPDX identifier.

137

138

Args:

139

license_id: SPDX license identifier (case-insensitive)

140

141

Returns:

142

License object with metadata

143

144

Raises:

145

ValueError: If license ID is not found

146

147

Example:

148

>>> mit = license_by_id("MIT")

149

>>> print(f"{mit.name} (OSI: {mit.is_osi_approved})")

150

MIT License (OSI: True)

151

152

>>> apache = license_by_id("apache-2.0") # Case insensitive

153

>>> print(apache.name)

154

Apache License 2.0

155

156

>>> # Check if license exists

157

>>> try:

158

... license = license_by_id("UNKNOWN")

159

... except ValueError:

160

... print("License not found")

161

"""

162

``` { .api }

163

164

## Helper Utilities

165

166

### combine_unicode

167

168

```python

169

def combine_unicode(string: str) -> str:

170

"""

171

Normalize Unicode string using NFC normalization.

172

173

Args:

174

string: String to normalize

175

176

Returns:

177

Normalized Unicode string

178

179

Note:

180

Uses Unicode NFC (Canonical Decomposition + Canonical Composition)

181

normalization to ensure consistent string representation.

182

183

Example:

184

>>> # Combining characters

185

>>> combined = combine_unicode("café") # e + ́ (combining acute)

186

>>> print(repr(combined)) # Single é character

187

188

>>> # Already normalized

189

>>> normal = combine_unicode("café") # Single é character

190

>>> combined == normal # True

191

"""

192

``` { .api }

193

194

### readme_content_type

195

196

```python

197

def readme_content_type(readme_path: Path) -> str:

198

"""

199

Detect README content type from file extension.

200

201

Args:

202

readme_path: Path to README file

203

204

Returns:

205

MIME content type string

206

207

Supported Extensions:

208

- .md, .markdown -> "text/markdown"

209

- .rst -> "text/x-rst"

210

- .txt -> "text/plain"

211

- Other -> "text/plain"

212

213

Example:

214

>>> content_type = readme_content_type(Path("README.md"))

215

>>> print(content_type)

216

text/markdown

217

218

>>> content_type = readme_content_type(Path("README.rst"))

219

>>> print(content_type)

220

text/x-rst

221

222

>>> content_type = readme_content_type(Path("README.txt"))

223

>>> print(content_type)

224

text/plain

225

"""

226

``` { .api }

227

228

### temporary_directory

229

230

```python

231

@contextmanager

232

def temporary_directory() -> Iterator[Path]:

233

"""

234

Context manager for temporary directory creation.

235

236

Yields:

237

Path to temporary directory

238

239

Note:

240

Directory is automatically cleaned up when context exits.

241

242

Example:

243

>>> from poetry.core.utils.helpers import temporary_directory

244

>>>

245

>>> with temporary_directory() as tmp_dir:

246

... print(f"Temp dir: {tmp_dir}")

247

... # Use temporary directory

248

... temp_file = tmp_dir / "test.txt"

249

... temp_file.write_text("Hello")

250

... # Directory automatically cleaned up here

251

"""

252

``` { .api }

253

254

## Version Helpers

255

256

### format_python_constraint

257

258

Formats Python version constraints into proper constraint strings, transforming disjunctive constraints into readable forms.

259

260

```python { .api }

261

def format_python_constraint(constraint: VersionConstraint) -> str:

262

"""

263

Format Python version constraint for display.

264

265

Transforms disjunctive version constraints into proper constraint strings

266

suitable for Python version specifications.

267

268

Args:

269

constraint: Version constraint to format

270

271

Returns:

272

Formatted constraint string (e.g., ">=3.8, !=3.9.*")

273

274

Examples:

275

- Version("3.8") -> "~3.8"

276

- Version("3") -> "^3.0"

277

- Complex unions -> ">=3.8, !=3.9.*, !=3.10.*"

278

"""

279

```

280

281

### PYTHON_VERSION

282

283

Comprehensive list of supported Python version patterns for constraint formatting.

284

285

```python { .api }

286

PYTHON_VERSION: list[str] = [

287

"2.7.*",

288

"3.0.*", "3.1.*", "3.2.*", "3.3.*", "3.4.*", "3.5.*",

289

"3.6.*", "3.7.*", "3.8.*", "3.9.*", "3.10.*", "3.11.*",

290

"3.12.*", "3.13.*"

291

]

292

```

293

294

## VCS Support

295

296

### Git

297

298

```python

299

class Git:

300

"""

301

Git operations wrapper.

302

303

Provides interface for common Git operations needed

304

for VCS dependency handling and project management.

305

"""

306

307

def __init__(self, work_dir: Path | None = None) -> None:

308

"""

309

Initialize Git wrapper.

310

311

Args:

312

work_dir: Working directory for Git operations

313

"""

314

315

@classmethod

316

def clone(

317

cls,

318

repository: str,

319

dest: Path,

320

branch: str | None = None,

321

tag: str | None = None,

322

) -> Git:

323

"""

324

Clone Git repository.

325

326

Args:

327

repository: Repository URL

328

dest: Destination directory

329

branch: Branch to checkout

330

tag: Tag to checkout

331

332

Returns:

333

Git instance for cloned repository

334

335

Raises:

336

GitError: If clone operation fails

337

"""

338

339

def checkout(self, rev: str) -> None:

340

"""

341

Checkout specific revision.

342

343

Args:

344

rev: Revision (branch, tag, commit hash)

345

346

Raises:

347

GitError: If checkout fails

348

"""

349

350

def rev_parse(self, rev: str) -> str:

351

"""

352

Get full commit hash for revision.

353

354

Args:

355

rev: Revision to resolve

356

357

Returns:

358

Full commit hash

359

360

Raises:

361

GitError: If revision cannot be resolved

362

"""

363

364

@property

365

def head(self) -> str:

366

"""Current HEAD commit hash."""

367

368

@property

369

def is_clean(self) -> bool:

370

"""Whether working directory is clean (no uncommitted changes)."""

371

``` { .api }

372

373

### GitConfig

374

375

```python

376

class GitConfig:

377

"""

378

Git configuration management.

379

380

Provides access to Git configuration values

381

with fallback to global and system configs.

382

"""

383

384

def get(self, key: str, default: str | None = None) -> str | None:

385

"""

386

Get Git configuration value.

387

388

Args:

389

key: Configuration key (e.g., "user.name", "user.email")

390

default: Default value if key not found

391

392

Returns:

393

Configuration value or default

394

395

Example:

396

>>> config = GitConfig()

397

>>> name = config.get("user.name")

398

>>> email = config.get("user.email")

399

>>> print(f"Git user: {name} <{email}>")

400

"""

401

``` { .api }

402

403

### ParsedUrl

404

405

```python

406

class ParsedUrl:

407

"""

408

Parsed Git URL representation.

409

410

Handles various Git URL formats and provides

411

normalized access to URL components.

412

"""

413

414

@classmethod

415

def parse(cls, url: str) -> ParsedUrl:

416

"""

417

Parse Git URL into components.

418

419

Args:

420

url: Git URL in various formats

421

422

Returns:

423

ParsedUrl instance with normalized components

424

425

Supported Formats:

426

- HTTPS: https://github.com/user/repo.git

427

- SSH: git@github.com:user/repo.git

428

- GitHub shorthand: github:user/repo

429

430

Example:

431

>>> parsed = ParsedUrl.parse("git@github.com:user/repo.git")

432

>>> print(parsed.url) # Normalized URL

433

>>> print(parsed.hostname) # github.com

434

"""

435

436

@property

437

def url(self) -> str:

438

"""Normalized Git URL."""

439

440

@property

441

def hostname(self) -> str:

442

"""Git server hostname."""

443

444

@property

445

def pathname(self) -> str:

446

"""Repository path."""

447

``` { .api }

448

449

### GitError

450

451

```python

452

class GitError(RuntimeError):

453

"""

454

Git operation error.

455

456

Raised when Git operations fail, providing

457

context about the specific operation and error.

458

"""

459

460

def __init__(self, message: str, return_code: int | None = None) -> None:

461

"""

462

Create Git error.

463

464

Args:

465

message: Error message

466

return_code: Git command return code

467

"""

468

``` { .api }

469

470

## Compatibility Utilities

471

472

### Platform Detection

473

474

```python

475

WINDOWS: bool

476

"""

477

Platform detection constant.

478

479

True if running on Windows, False otherwise.

480

Used for platform-specific code paths.

481

482

Example:

483

>>> from poetry.core.utils._compat import WINDOWS

484

>>> if WINDOWS:

485

... print("Running on Windows")

486

... else:

487

... print("Running on Unix-like system")

488

"""

489

``` { .api }

490

491

### TOML Support

492

493

```python

494

tomllib: ModuleType

495

"""

496

TOML parsing library compatibility layer.

497

498

Uses tomllib (Python 3.11+) or tomli (older Python versions).

499

Provides consistent TOML parsing interface across Python versions.

500

501

Example:

502

>>> from poetry.core.utils._compat import tomllib

503

>>>

504

>>> # Parse TOML file

505

>>> with open("pyproject.toml", "rb") as f:

506

... data = tomllib.load(f)

507

508

>>> # Parse TOML string

509

>>> toml_string = '''

510

... [tool.poetry]

511

... name = "my-package"

512

... '''

513

>>> data = tomllib.loads(toml_string)

514

"""

515

``` { .api }

516

517

## Usage Examples

518

519

### Configuration Validation

520

521

```python

522

from poetry.core.json import validate_object, ValidationError

523

from poetry.core.pyproject.toml import PyProjectTOML

524

525

def validate_poetry_config(pyproject_path: Path):

526

"""Validate Poetry configuration with detailed error reporting."""

527

528

try:

529

# Load configuration

530

pyproject = PyProjectTOML(pyproject_path)

531

poetry_config = pyproject.poetry_config

532

533

# Validate against schema

534

errors = validate_object(poetry_config, "poetry-schema")

535

536

if not errors:

537

print("✅ Configuration is valid")

538

return True

539

540

print("❌ Configuration validation failed:")

541

for error in errors:

542

print(f" • {error}")

543

544

return False

545

546

except ValidationError as e:

547

print(f"❌ Validation error: {e}")

548

if hasattr(e, 'errors') and e.errors:

549

for error in e.errors:

550

print(f" • {error}")

551

return False

552

553

except Exception as e:

554

print(f"❌ Unexpected error: {e}")

555

return False

556

557

# Usage

558

is_valid = validate_poetry_config(Path("pyproject.toml"))

559

``` { .api }

560

561

### License Information

562

563

```python

564

from poetry.core.spdx.helpers import license_by_id

565

566

def show_license_info(license_ids: list[str]):

567

"""Display information about SPDX licenses."""

568

569

print("📜 License Information:")

570

print("=" * 50)

571

572

for license_id in license_ids:

573

try:

574

license_info = license_by_id(license_id)

575

576

print(f"\n🔖 {license_info.id}")

577

print(f" Name: {license_info.name}")

578

print(f" OSI Approved: {'✅' if license_info.is_osi_approved else '❌'}")

579

print(f" Deprecated: {'⚠️' if license_info.is_deprecated else '✅'}")

580

581

except ValueError:

582

print(f"\n❌ {license_id}: License not found")

583

584

# Common licenses

585

common_licenses = [

586

"MIT", "Apache-2.0", "GPL-3.0-or-later",

587

"BSD-3-Clause", "ISC", "LGPL-2.1"

588

]

589

590

show_license_info(common_licenses)

591

``` { .api }

592

593

### File Type Detection

594

595

```python

596

from pathlib import Path

597

from poetry.core.utils.helpers import readme_content_type

598

599

def analyze_readme_files(project_dir: Path):

600

"""Analyze README files in project directory."""

601

602

readme_patterns = ["README*", "readme*", "Readme*"]

603

readme_files = []

604

605

for pattern in readme_patterns:

606

readme_files.extend(project_dir.glob(pattern))

607

608

if not readme_files:

609

print("❌ No README files found")

610

return

611

612

print("📄 README Files Analysis:")

613

print("=" * 40)

614

615

for readme_file in readme_files:

616

if readme_file.is_file():

617

content_type = readme_content_type(readme_file)

618

size = readme_file.stat().st_size

619

620

print(f"\n📝 {readme_file.name}")

621

print(f" Content-Type: {content_type}")

622

print(f" Size: {size:,} bytes")

623

624

# Preview content

625

try:

626

with readme_file.open('r', encoding='utf-8') as f:

627

preview = f.read(200).strip()

628

if len(preview) == 200:

629

preview += "..."

630

print(f" Preview: {preview[:50]}...")

631

except UnicodeDecodeError:

632

print(" Preview: <Binary content>")

633

634

# Usage

635

analyze_readme_files(Path("./my-project"))

636

``` { .api }

637

638

### VCS Operations

639

640

```python

641

from pathlib import Path

642

from poetry.core.vcs.git import Git, GitError, ParsedUrl

643

644

def clone_and_analyze_repo(repo_url: str, target_dir: Path):

645

"""Clone repository and analyze its structure."""

646

647

try:

648

# Parse repository URL

649

parsed_url = ParsedUrl.parse(repo_url)

650

print(f"🔗 Repository: {parsed_url.url}")

651

print(f" Host: {parsed_url.hostname}")

652

print(f" Path: {parsed_url.pathname}")

653

654

# Clone repository

655

print(f"\n📥 Cloning to {target_dir}...")

656

git = Git.clone(repo_url, target_dir)

657

658

# Get repository information

659

head_commit = git.head

660

is_clean = git.is_clean

661

662

print(f"✅ Clone successful")

663

print(f" HEAD: {head_commit[:8]}")

664

print(f" Clean: {'✅' if is_clean else '❌'}")

665

666

# Check for pyproject.toml

667

pyproject_file = target_dir / "pyproject.toml"

668

if pyproject_file.exists():

669

print(f"📋 Found pyproject.toml")

670

671

# Quick analysis

672

from poetry.core.pyproject.toml import PyProjectTOML

673

try:

674

pyproject = PyProjectTOML(pyproject_file)

675

if pyproject.is_poetry_project():

676

config = pyproject.poetry_config

677

print(f" Poetry project: {config.get('name', 'Unknown')}")

678

else:

679

print(f" Not a Poetry project")

680

except Exception as e:

681

print(f" Error reading config: {e}")

682

else:

683

print(f"❌ No pyproject.toml found")

684

685

return git

686

687

except GitError as e:

688

print(f"❌ Git error: {e}")

689

return None

690

691

except Exception as e:

692

print(f"❌ Unexpected error: {e}")

693

return None

694

695

# Usage

696

git = clone_and_analyze_repo(

697

"https://github.com/python-poetry/poetry-core.git",

698

Path("./temp-clone")

699

)

700

``` { .api }

701

702

### Temporary File Operations

703

704

```python

705

from poetry.core.utils.helpers import temporary_directory

706

from pathlib import Path

707

import shutil

708

709

def process_with_temp_workspace(source_files: list[Path]):

710

"""Process files using temporary workspace."""

711

712

with temporary_directory() as temp_dir:

713

print(f"🛠️ Working in: {temp_dir}")

714

715

# Copy source files to temp directory

716

temp_files = []

717

for source_file in source_files:

718

if source_file.exists():

719

temp_file = temp_dir / source_file.name

720

shutil.copy2(source_file, temp_file)

721

temp_files.append(temp_file)

722

print(f" Copied: {source_file.name}")

723

724

# Process files in temp directory

725

print(f"🔄 Processing {len(temp_files)} files...")

726

results = []

727

728

for temp_file in temp_files:

729

try:

730

# Example processing: count lines

731

with temp_file.open('r') as f:

732

lines = len(f.readlines())

733

734

results.append({

735

"file": temp_file.name,

736

"lines": lines,

737

"size": temp_file.stat().st_size

738

})

739

740

print(f" {temp_file.name}: {lines} lines")

741

742

except Exception as e:

743

print(f" ❌ Error processing {temp_file.name}: {e}")

744

745

# Temporary directory is automatically cleaned up

746

print(f"✅ Processing complete, temp directory cleaned")

747

return results

748

749

# Usage

750

source_files = [

751

Path("README.md"),

752

Path("pyproject.toml"),

753

Path("src/mypackage/__init__.py")

754

]

755

756

results = process_with_temp_workspace(source_files)

757

``` { .api }

758

759

### Unicode Normalization

760

761

```python

762

from poetry.core.utils.helpers import combine_unicode

763

import unicodedata

764

765

def normalize_project_strings(config: dict):

766

"""Normalize Unicode strings in project configuration."""

767

768

print("🔤 Unicode Normalization:")

769

770

string_fields = ["name", "description", "authors", "maintainers"]

771

772

for field in string_fields:

773

if field in config:

774

original = config[field]

775

776

if isinstance(original, str):

777

normalized = combine_unicode(original)

778

779

# Check if normalization changed anything

780

if original != normalized:

781

print(f" {field}: Normalized")

782

print(f" Before: {repr(original)}")

783

print(f" After: {repr(normalized)}")

784

config[field] = normalized

785

else:

786

print(f" {field}: Already normalized")

787

788

elif isinstance(original, list):

789

# Handle list of strings (authors, maintainers)

790

normalized_list = []

791

changed = False

792

793

for item in original:

794

if isinstance(item, str):

795

normalized_item = combine_unicode(item)

796

normalized_list.append(normalized_item)

797

if item != normalized_item:

798

changed = True

799

else:

800

normalized_list.append(item)

801

802

if changed:

803

print(f" {field}: Normalized list items")

804

config[field] = normalized_list

805

else:

806

print(f" {field}: List already normalized")

807

808

return config

809

810

# Example usage

811

project_config = {

812

"name": "café-package", # May contain combining characters

813

"description": "A café management system",

814

"authors": ["José García <jose@example.com>"]

815

}

816

817

normalized_config = normalize_project_strings(project_config)

818

``` { .api }

819

820

## Error Handling Utilities

821

822

### Safe Operations

823

824

```python

825

from poetry.core.json import ValidationError

826

from poetry.core.spdx.helpers import license_by_id

827

from poetry.core.vcs.git import GitError

828

829

def safe_validation_operations():

830

"""Demonstrate safe utility operations with error handling."""

831

832

# Safe license lookup

833

def safe_license_lookup(license_id: str):

834

try:

835

return license_by_id(license_id)

836

except ValueError:

837

print(f"❌ Unknown license: {license_id}")

838

return None

839

840

# Safe validation

841

def safe_validation(obj: dict, schema: str):

842

try:

843

from poetry.core.json import validate_object

844

errors = validate_object(obj, schema)

845

return errors

846

except ValidationError as e:

847

print(f"❌ Validation failed: {e}")

848

return [str(e)]

849

except Exception as e:

850

print(f"❌ Unexpected validation error: {e}")

851

return [f"Unexpected error: {e}"]

852

853

# Safe Git operations

854

def safe_git_operation(func, *args, **kwargs):

855

try:

856

return func(*args, **kwargs)

857

except GitError as e:

858

print(f"❌ Git operation failed: {e}")

859

return None

860

except Exception as e:

861

print(f"❌ Unexpected Git error: {e}")

862

return None

863

864

# Usage examples

865

license_info = safe_license_lookup("MIT")

866

if license_info:

867

print(f"✅ License found: {license_info.name}")

868

869

validation_errors = safe_validation({"name": "test"}, "poetry-schema")

870

if not validation_errors:

871

print("✅ Validation passed")

872

else:

873

print(f"❌ Validation errors: {validation_errors}")

874

875

safe_validation_operations()

876

``` { .api }

877

878

## Type Definitions

879

880

```python

881

from typing import Any, Dict, List, Iterator

882

from pathlib import Path

883

884

# Validation types

885

ValidationErrors = List[str]

886

SchemaName = str

887

888

# License types

889

LicenseId = str

890

891

# VCS types

892

GitUrl = str

893

GitRevision = str

894

GitBranch = str

895

GitTag = str

896

897

# Utility types

898

ContentType = str

899

UnicodeString = str

900

``` { .api }