Skip to content

Editors API ​

The editor package provides powerful tools for editing and manipulating Python requirements.txt files.

Overview ​

The editor package offers three different editors, each optimized for different use cases:

go
import "github.com/scagogogo/python-requirements-parser/pkg/editor"

Editor Comparison ​

FeaturePositionAwareEditorVersionEditorV2VersionEditor
Minimal Diff✅ Best❌ No❌ No
Format Preservation✅ Perfect✅ Good⚠️ Basic
Performance✅ Fastest✅ Fast⚠️ Slower
Memory Usage✅ Lowest✅ Low⚠️ Higher
Complex Editing⚠️ Limited✅ Full✅ Full
Use CaseProduction updatesDevelopment toolsSimple scripts

PositionAwareEditor ​

The PositionAwareEditor is the recommended editor for production environments where minimal changes are crucial.

Key Features ​

  • Minimal diff editing - Only changes what's necessary
  • Perfect format preservation - Maintains comments, spacing, and structure
  • High performance - Nanosecond-level update operations
  • Zero allocations - Batch updates with no memory allocations

Constructor ​

go
func NewPositionAwareEditor() *PositionAwareEditor

Example:

go
editor := editor.NewPositionAwareEditor()

Core Methods ​

ParseRequirementsFile() ​

Parses a requirements file and creates a position-aware document.

go
func (e *PositionAwareEditor) ParseRequirementsFile(content string) (*PositionAwareDocument, error)

Parameters:

  • content (string): Requirements file content

Returns:

  • *PositionAwareDocument: Document with position information
  • error: Parse error if any

Example:

go
content := `flask==1.0.0  # Web framework
django>=3.2.0  # Another framework`

doc, err := editor.ParseRequirementsFile(content)
if err != nil {
    log.Fatal(err)
}

UpdatePackageVersion() ​

Updates a single package version with minimal changes.

go
func (e *PositionAwareEditor) UpdatePackageVersion(doc *PositionAwareDocument, packageName, newVersion string) error

Parameters:

  • doc (*PositionAwareDocument): Document to update
  • packageName (string): Name of package to update
  • newVersion (string): New version constraint

Returns:

  • error: Error if package not found or invalid version

Example:

go
err := editor.UpdatePackageVersion(doc, "flask", "==2.0.1")
if err != nil {
    log.Fatal(err)
}

BatchUpdateVersions() ​

Updates multiple packages in a single operation.

go
func (e *PositionAwareEditor) BatchUpdateVersions(doc *PositionAwareDocument, updates map[string]string) error

Parameters:

  • doc (*PositionAwareDocument): Document to update
  • updates (map[string]string): Map of package names to new versions

Returns:

  • error: Error if any updates fail

Example:

go
updates := map[string]string{
    "flask":   "==2.0.1",
    "django":  ">=3.2.13",
    "requests": ">=2.28.0",
}

err := editor.BatchUpdateVersions(doc, updates)
if err != nil {
    log.Printf("Some updates failed: %v", err)
}

SerializeToString() ​

Serializes the document back to string format with minimal changes.

go
func (e *PositionAwareEditor) SerializeToString(doc *PositionAwareDocument) string

Parameters:

  • doc (*PositionAwareDocument): Document to serialize

Returns:

  • string: Updated requirements content

Example:

go
result := editor.SerializeToString(doc)
fmt.Println(result)

GetPackageInfo() ​

Retrieves information about a specific package.

go
func (e *PositionAwareEditor) GetPackageInfo(doc *PositionAwareDocument, packageName string) (*models.Requirement, error)

Example:

go
info, err := editor.GetPackageInfo(doc, "flask")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Flask version: %s\n", info.Version)

ListPackages() ​

Lists all packages in the document.

go
func (e *PositionAwareEditor) ListPackages(doc *PositionAwareDocument) []*models.Requirement

Example:

go
packages := editor.ListPackages(doc)
fmt.Printf("Found %d packages\n", len(packages))

Performance Characteristics ​

OperationTimeMemoryAllocations
Single update67.67 ns8 B1 alloc
Batch update (10 packages)374.1 ns0 B0 allocs
Serialize (100 packages)4.3 µs8.2 KB102 allocs

Real-World Example ​

go
editor := editor.NewPositionAwareEditor()

// Original content with complex formatting
content := `# Production dependencies
flask==1.0.0  # Web framework
django[rest,auth]>=3.2.0,<4.0.0  # Web framework with extras

# VCS dependencies (preserved)
git+https://github.com/user/project.git#egg=project

# Environment markers (preserved)
pywin32>=1.0; platform_system == "Windows"`

doc, err := editor.ParseRequirementsFile(content)
if err != nil {
    log.Fatal(err)
}

// Security updates
updates := map[string]string{
    "flask":  "==2.0.1",
    "django": ">=3.2.13,<4.0.0",
}

err = editor.BatchUpdateVersions(doc, updates)
if err != nil {
    log.Fatal(err)
}

result := editor.SerializeToString(doc)
// Only 2 lines changed, all formatting preserved

VersionEditorV2 ​

The VersionEditorV2 provides comprehensive editing capabilities with full parser support.

Key Features ​

  • Full editing capabilities - Add, remove, update packages
  • Parser-based - Understands all pip formats
  • Good performance - Optimized for development workflows
  • Comprehensive API - Supports extras, markers, and all package types

Constructor ​

go
func NewVersionEditorV2() *VersionEditorV2

Core Methods ​

ParseRequirementsFile() ​

go
func (v *VersionEditorV2) ParseRequirementsFile(content string) (*RequirementsDocument, error)

UpdatePackageVersion() ​

go
func (v *VersionEditorV2) UpdatePackageVersion(doc *RequirementsDocument, packageName, version string) error

AddPackage() ​

Adds a new package with full specification.

go
func (v *VersionEditorV2) AddPackage(doc *RequirementsDocument, packageName, version string, extras []string, markers string) error

Example:

go
err := editor.AddPackage(doc, "fastapi", ">=0.95.0", 
    []string{"all"}, `python_version >= "3.7"`)

RemovePackage() ​

Removes a package from the requirements.

go
func (v *VersionEditorV2) RemovePackage(doc *RequirementsDocument, packageName string) error

Example:

go
err := editor.RemovePackage(doc, "old-package")

UpdatePackageExtras() ​

Updates the extras for a package.

go
func (v *VersionEditorV2) UpdatePackageExtras(doc *RequirementsDocument, packageName string, extras []string) error

Example:

go
err := editor.UpdatePackageExtras(doc, "django", []string{"rest", "auth"})

UpdatePackageMarkers() ​

Updates the environment markers for a package.

go
func (v *VersionEditorV2) UpdatePackageMarkers(doc *RequirementsDocument, packageName, markers string) error

Example:

go
err := editor.UpdatePackageMarkers(doc, "pywin32", `platform_system == "Windows"`)

Full Example ​

go
editor := editor.NewVersionEditorV2()

content := `flask==1.0.0
django>=3.2.0`

doc, err := editor.ParseRequirementsFile(content)
if err != nil {
    log.Fatal(err)
}

// Add new package with extras and markers
err = editor.AddPackage(doc, "fastapi", ">=0.95.0", 
    []string{"all"}, `python_version >= "3.7"`)
if err != nil {
    log.Fatal(err)
}

// Update existing package
err = editor.UpdatePackageVersion(doc, "flask", "==2.0.1")
if err != nil {
    log.Fatal(err)
}

// Add extras to existing package
err = editor.UpdatePackageExtras(doc, "django", []string{"rest", "auth"})
if err != nil {
    log.Fatal(err)
}

result := editor.SerializeToString(doc)
fmt.Println(result)

VersionEditor ​

The VersionEditor provides basic text-based editing capabilities.

Key Features ​

  • Simple API - Easy to use for basic operations
  • Text-based - Works directly with version strings
  • Lightweight - Minimal dependencies

Constructor ​

go
func NewVersionEditor() *VersionEditor

Core Methods ​

SetExactVersion() ​

Sets an exact version constraint.

go
func (v *VersionEditor) SetExactVersion(req *models.Requirement, version string) (*models.Requirement, error)

SetMinimumVersion() ​

Sets a minimum version constraint.

go
func (v *VersionEditor) SetMinimumVersion(req *models.Requirement, version string) (*models.Requirement, error)

SetCompatibleVersion() ​

Sets a compatible version constraint (~=).

go
func (v *VersionEditor) SetCompatibleVersion(req *models.Requirement, version string) (*models.Requirement, error)

AppendVersionSpecifier() ​

Adds additional version constraints.

go
func (v *VersionEditor) AppendVersionSpecifier(req *models.Requirement, specifier string) (*models.Requirement, error)

Example ​

go
editor := editor.NewVersionEditor()

req := &models.Requirement{
    Name:    "flask",
    Version: ">=1.0.0",
}

// Set exact version
updatedReq, err := editor.SetExactVersion(req, "2.0.1")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Updated: %s %s\n", updatedReq.Name, updatedReq.Version)
// Output: Updated: flask ==2.0.1

Best Practices ​

Choosing the Right Editor ​

  1. Use PositionAwareEditor for:

    • Production deployments
    • Security updates
    • Minimal diff requirements
    • CI/CD pipelines
  2. Use VersionEditorV2 for:

    • Development tools
    • Package management utilities
    • Complex editing operations
    • Adding/removing packages
  3. Use VersionEditor for:

    • Simple scripts
    • Basic version updates
    • Learning and prototyping

Performance Optimization ​

go
// Efficient: Reuse editor instances
editor := editor.NewPositionAwareEditor()
for _, file := range files {
    doc, err := editor.ParseRequirementsFile(file.Content)
    // Process...
}

// Efficient: Batch updates
updates := map[string]string{
    "package1": "==1.0.0",
    "package2": "==2.0.0",
    "package3": "==3.0.0",
}
err := editor.BatchUpdateVersions(doc, updates)

// Less efficient: Individual updates
for pkg, version := range updates {
    err := editor.UpdatePackageVersion(doc, pkg, version) // ❌
}

Error Handling ​

go
// Handle package not found
err := editor.UpdatePackageVersion(doc, "nonexistent", "==1.0.0")
if err != nil {
    if strings.Contains(err.Error(), "not found") {
        log.Printf("Package not found, skipping: %v", err)
    } else {
        log.Fatalf("Update failed: %v", err)
    }
}

// Handle batch update failures
err = editor.BatchUpdateVersions(doc, updates)
if err != nil {
    log.Printf("Some updates failed: %v", err)
    // Continue with successful updates
}

Thread Safety ​

  • Editor instances are safe for concurrent use
  • Document objects are NOT thread-safe
go
// Safe: Multiple goroutines using same editor
editor := editor.NewPositionAwareEditor()
go func() { doc, _ := editor.ParseRequirementsFile(content1) }()
go func() { doc, _ := editor.ParseRequirementsFile(content2) }()

// NOT safe: Sharing document between goroutines
doc, _ := editor.ParseRequirementsFile(content)
go func() { editor.UpdatePackageVersion(doc, "pkg1", "==1.0") }() // ❌
go func() { editor.UpdatePackageVersion(doc, "pkg2", "==2.0") }() // ❌

Next Steps ​

Released under the MIT License