Skip to content

Parser API ​

The parser package provides the core functionality for parsing Python requirements.txt files.

Overview ​

The Parser struct is the main entry point for parsing requirements.txt files. It supports various parsing options and can handle all pip-compatible formats.

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

Parser Struct ​

go
type Parser struct {
    // RecursiveResolve enables parsing of referenced files (-r/--requirement)
    RecursiveResolve bool
    
    // ProcessEnvVars enables environment variable substitution
    ProcessEnvVars bool
}

Fields ​

RecursiveResolve ​

  • Type: bool
  • Default: false
  • Description: When enabled, the parser will automatically parse files referenced with -r or --requirement directives.
go
p := parser.New()
p.RecursiveResolve = true

// Will parse main file and any referenced files
reqs, err := p.ParseFile("requirements.txt")

ProcessEnvVars ​

  • Type: bool
  • Default: false
  • Description: When enabled, environment variables in the format ${VAR_NAME} will be replaced with their actual values.
go
p := parser.New()
p.ProcessEnvVars = true

// ${VERSION} will be replaced with the environment variable value
reqs, err := p.ParseString("flask==${VERSION}")

Constructor Functions ​

New() ​

Creates a new parser with default settings.

go
func New() *Parser

Returns: A new Parser instance with default configuration.

Example:

go
p := parser.New()
reqs, err := p.ParseFile("requirements.txt")

NewWithRecursiveResolve() ​

Creates a new parser with recursive file resolution enabled.

go
func NewWithRecursiveResolve() *Parser

Returns: A new Parser instance with RecursiveResolve set to true.

Example:

go
p := parser.NewWithRecursiveResolve()
// Will automatically parse referenced files
reqs, err := p.ParseFile("requirements.txt")

Parsing Methods ​

ParseFile() ​

Parses a requirements.txt file from the filesystem.

go
func (p *Parser) ParseFile(filePath string) ([]*models.Requirement, error)

Parameters:

  • filePath (string): Path to the requirements.txt file

Returns:

  • []*models.Requirement: Slice of parsed requirements
  • error: Error if parsing fails

Example:

go
p := parser.New()
reqs, err := p.ParseFile("requirements.txt")
if err != nil {
    log.Fatalf("Failed to parse file: %v", err)
}

for _, req := range reqs {
    if !req.IsComment && !req.IsEmpty {
        fmt.Printf("Package: %s, Version: %s\n", req.Name, req.Version)
    }
}

Error Cases:

  • File does not exist
  • File cannot be read
  • Invalid syntax in file

ParseString() ​

Parses requirements from a string.

go
func (p *Parser) ParseString(content string) ([]*models.Requirement, error)

Parameters:

  • content (string): Requirements content as string

Returns:

  • []*models.Requirement: Slice of parsed requirements
  • error: Error if parsing fails

Example:

go
content := `
flask==2.0.1
django>=3.2.0,<4.0.0
requests>=2.25.0  # HTTP library
# Development dependencies
pytest>=6.0.0
`

p := parser.New()
reqs, err := p.ParseString(content)
if err != nil {
    log.Fatalf("Failed to parse string: %v", err)
}

fmt.Printf("Found %d requirements\n", len(reqs))

Parse() ​

Parses requirements from an io.Reader.

go
func (p *Parser) Parse(reader io.Reader) ([]*models.Requirement, error)

Parameters:

  • reader (io.Reader): Source of requirements content

Returns:

  • []*models.Requirement: Slice of parsed requirements
  • error: Error if parsing fails

Example:

go
file, err := os.Open("requirements.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

p := parser.New()
reqs, err := p.Parse(file)
if err != nil {
    log.Fatalf("Failed to parse: %v", err)
}

Supported Formats ​

The parser supports all pip-compatible requirement formats:

Basic Dependencies ​

txt
flask==2.0.1
django>=3.2.0
requests~=2.25.0
numpy>=1.20.0,<1.22.0

Dependencies with Extras ​

txt
django[rest,auth]>=3.2.0
uvicorn[standard]>=0.15.0
requests[security,socks]>=2.25.0

Environment Markers ​

txt
pywin32>=1.0; platform_system == "Windows"
dataclasses>=0.6; python_version < "3.7"
typing-extensions>=3.7.4; python_version < "3.8"

VCS Dependencies ​

txt
git+https://github.com/user/project.git#egg=project
git+https://github.com/user/project.git@v1.2.3#egg=project
-e git+https://github.com/user/project.git@develop#egg=project
hg+https://bitbucket.org/user/project#egg=project
svn+https://svn.example.com/project/trunk#egg=project

URL Dependencies ​

txt
https://example.com/package.whl
http://mirrors.aliyun.com/pypi/web/package-1.0.0.tar.gz
file:///path/to/local/package.whl

File References ​

txt
-r requirements-dev.txt
--requirement requirements-prod.txt
-c constraints.txt
--constraint constraints-prod.txt

Global Options ​

txt
--index-url https://pypi.example.com/simple/
--extra-index-url https://private.pypi.com/simple/
--trusted-host pypi.example.com
--find-links https://download.pytorch.org/whl/torch_stable.html

Hash Verification ​

txt
flask==2.0.1 --hash=sha256:abcdef1234567890
django==3.2.13 \
    --hash=sha256:1234567890abcdef \
    --hash=sha256:fedcba0987654321

Comments and Empty Lines ​

txt
# Production dependencies
flask==2.0.1  # Web framework

# Development dependencies
pytest>=6.0.0  # Testing framework

# Empty lines are preserved

Advanced Usage ​

Recursive File Parsing ​

When RecursiveResolve is enabled, the parser will automatically follow file references:

go
p := parser.NewWithRecursiveResolve()
reqs, err := p.ParseFile("requirements.txt")

// If requirements.txt contains:
// -r requirements-dev.txt
// -r requirements-prod.txt
// 
// The parser will automatically parse those files too

Environment Variable Substitution ​

go
// Set environment variable
os.Setenv("DJANGO_VERSION", "3.2.13")

p := parser.New()
p.ProcessEnvVars = true

reqs, err := p.ParseString("django==${DJANGO_VERSION}")
// Results in: django==3.2.13

Custom Configuration ​

go
p := parser.New()
p.RecursiveResolve = true
p.ProcessEnvVars = true

// Now supports both recursive parsing and environment variables
reqs, err := p.ParseFile("requirements.txt")

Performance ​

Benchmarks ​

OperationTime (100 packages)MemoryAllocations
ParseString357 µs480 KB4301 allocs
ParseFile400 µs485 KB4305 allocs
Parse (io.Reader)365 µs482 KB4303 allocs

Scaling ​

Package CountParse TimeMemory Usage
100357 µs480 KB
5002.6 ms2.1 MB
10007.0 ms4.8 MB
200020.4 ms12.2 MB

Performance Tips ​

  1. Reuse parser instances for multiple files
  2. Disable recursive parsing if not needed
  3. Disable environment variable processing if not needed
  4. Use ParseString for in-memory content
go
// Efficient: Reuse parser
p := parser.New()
for _, file := range files {
    reqs, err := p.ParseFile(file)
    // Process reqs...
}

// Less efficient: Create new parser each time
for _, file := range files {
    p := parser.New()  // ❌ Creates new parser each time
    reqs, err := p.ParseFile(file)
}

Error Handling ​

Common Errors ​

go
reqs, err := p.ParseFile("requirements.txt")
if err != nil {
    switch {
    case os.IsNotExist(err):
        log.Printf("File not found: %v", err)
    case os.IsPermission(err):
        log.Printf("Permission denied: %v", err)
    default:
        log.Printf("Parse error: %v", err)
    }
}

Validation ​

The parser is permissive and will parse most content, but you can validate results:

go
reqs, err := p.ParseString(content)
if err != nil {
    log.Fatal(err)
}

for _, req := range reqs {
    if !req.IsComment && !req.IsEmpty && req.Name == "" {
        log.Printf("Warning: Empty package name in line: %s", req.OriginalLine)
    }
}

Thread Safety ​

Parser instances are safe for concurrent use:

go
p := parser.New()

// Safe: Multiple goroutines using same parser
go func() {
    reqs, _ := p.ParseFile("requirements1.txt")
    // Process reqs...
}()

go func() {
    reqs, _ := p.ParseFile("requirements2.txt")
    // Process reqs...
}()

Next Steps ​

Released under the MIT License