Add Pip3 support (#75)

* add pip3 support

* add pip3 e2e

* chore: rename var
This commit is contained in:
Sahil Bansal
2025-12-03 20:28:45 +05:30
committed by GitHub
parent c47cb30de0
commit 50d7b55445
7 changed files with 122 additions and 2 deletions
+23
View File
@@ -187,6 +187,29 @@ jobs:
deactivate deactivate
cd .. && rm -rf pip-test cd .. && rm -rf pip-test
- name: Test Pip3 - Single Package & Manifest
run: |
echo "Testing Pip3 single package installation..."
mkdir pip3-test && cd pip3-test
python -m venv venv && source venv/bin/activate
pmg pip3 install requests
pmg pip3 install numpy==1.24.0
pmg pip3 freeze > requirements.txt
# Verification: requirements.txt contains expected packages
test -s requirements.txt
grep -E '^requests==' requirements.txt
grep -E '^numpy==' requirements.txt
echo "Testing Pip3 manifest installation..."
pmg pip3 uninstall -y requests numpy
pmg pip3 install -r requirements.txt
# Verification: imported packages are available in the environment
python -c "import requests, numpy; print(requests.__version__); print(numpy.__version__)"
deactivate
cd .. && rm -rf pip3-test
- name: Test UV - Single Package & Manifest - name: Test UV - Single Package & Manifest
run: | run: |
echo "Testing UV single package installation..." echo "Testing UV single package installation..."
+61
View File
@@ -0,0 +1,61 @@
package pypi
import (
"context"
"fmt"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/internal/analytics"
"github.com/safedep/pmg/internal/flows"
"github.com/safedep/pmg/internal/ui"
"github.com/safedep/pmg/packagemanager"
"github.com/spf13/cobra"
)
func NewPip3Command() *cobra.Command {
return &cobra.Command{
Use: "pip3 [action] [package]",
Short: "Guard pip3 package manager",
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := executePip3Flow(cmd.Context(), args)
if err != nil {
ui.ErrorExit(err)
}
return nil
},
}
}
func executePip3Flow(ctx context.Context, args []string) error {
analytics.TrackCommandPip3()
packageManager, err := packagemanager.NewPypiPackageManager(packagemanager.DefaultPip3PackageManagerConfig())
if err != nil {
return fmt.Errorf("failed to create pip3 package manager proxy: %w", err)
}
config, err := config.FromContext(ctx)
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
parsedCommand, err := packageManager.ParseCommand(args)
if err != nil {
return fmt.Errorf("failed to parse command: %w", err)
}
// Parse the args right here
packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig()
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
packageResolverConfig.PackageInstallTargets = parsedCommand.InstallTargets
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
if err != nil {
return fmt.Errorf("failed to create dependency resolver: %w", err)
}
return flows.Common(packageManager, packageResolver, config).Run(ctx, args, parsedCommand)
}
+2
View File
@@ -25,6 +25,7 @@ const (
Npm PackageManagerName = "npm" Npm PackageManagerName = "npm"
Pnpm PackageManagerName = "pnpm" Pnpm PackageManagerName = "pnpm"
Pip PackageManagerName = "pip" Pip PackageManagerName = "pip"
Pip3 PackageManagerName = "pip3"
Bun PackageManagerName = "bun" Bun PackageManagerName = "bun"
Yarn PackageManagerName = "yarn" Yarn PackageManagerName = "yarn"
Uv PackageManagerName = "uv" Uv PackageManagerName = "uv"
@@ -45,6 +46,7 @@ func NewExtractorManager() *ExtractorManager {
Yarn: &YarnExtractor{}, Yarn: &YarnExtractor{},
Uv: &UvExtractor{}, Uv: &UvExtractor{},
Poetry: &PoetryExtractor{}, Poetry: &PoetryExtractor{},
Pip3: &Pip3Extractor{},
}, },
} }
} }
+21 -2
View File
@@ -19,8 +19,8 @@ func (p *PipExtractor) GetPackageManager() PackageManagerName {
return Pip return Pip
} }
func (n *PipExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) { func (p *PipExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
return parseLockfile(lockfilePath, scanDir, n.GetEcosystem()) return parseLockfile(lockfilePath, scanDir, p.GetEcosystem())
} }
// UvExtractor handles uv.lock files // UvExtractor handles uv.lock files
@@ -60,3 +60,22 @@ func (p *PoetryExtractor) GetPackageManager() PackageManagerName {
func (p *PoetryExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) { func (p *PoetryExtractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
return parseLockfile(lockfilePath, scanDir, p.GetEcosystem()) return parseLockfile(lockfilePath, scanDir, p.GetEcosystem())
} }
// Pip3Extractor handles requirements.txt files
type Pip3Extractor struct{}
func (p *Pip3Extractor) GetSupportedFiles() []string {
return []string{"requirements.txt"}
}
func (p *Pip3Extractor) GetEcosystem() packagev1.Ecosystem {
return packagev1.Ecosystem_ECOSYSTEM_PYPI
}
func (p *Pip3Extractor) GetPackageManager() PackageManagerName {
return Pip3
}
func (p *Pip3Extractor) Extract(lockfilePath, scanDir string) ([]*packagev1.PackageVersion, error) {
return parseLockfile(lockfilePath, scanDir, p.GetEcosystem())
}
+5
View File
@@ -7,6 +7,7 @@ const (
eventCommandPnpm = "pmg_command_pnpm" eventCommandPnpm = "pmg_command_pnpm"
eventCommandYarn = "pmg_command_yarn" eventCommandYarn = "pmg_command_yarn"
eventCommandPip = "pmg_command_pip" eventCommandPip = "pmg_command_pip"
eventCommandPip3 = "pmg_command_pip3"
eventCommandUv = "pmg_command_uv" eventCommandUv = "pmg_command_uv"
eventCommandPoetry = "pmg_command_poetry" eventCommandPoetry = "pmg_command_poetry"
@@ -39,6 +40,10 @@ func TrackCommandPip() {
TrackEvent(eventCommandPip) TrackEvent(eventCommandPip)
} }
func TrackCommandPip3() {
TrackEvent(eventCommandPip3)
}
func TrackCommandUv() { func TrackCommandUv() {
TrackEvent(eventCommandUv) TrackEvent(eventCommandUv)
} }
+1
View File
@@ -94,6 +94,7 @@ func main() {
cmd.AddCommand(npm.NewBunCommand()) cmd.AddCommand(npm.NewBunCommand())
cmd.AddCommand(npm.NewYarnCommand()) cmd.AddCommand(npm.NewYarnCommand())
cmd.AddCommand(pypi.NewPipCommand()) cmd.AddCommand(pypi.NewPipCommand())
cmd.AddCommand(pypi.NewPip3Command())
cmd.AddCommand(pypi.NewUvCommand()) cmd.AddCommand(pypi.NewUvCommand())
cmd.AddCommand(pypi.NewPoetryCommand()) cmd.AddCommand(pypi.NewPoetryCommand())
cmd.AddCommand(version.NewVersionCommand()) cmd.AddCommand(version.NewVersionCommand())
+9
View File
@@ -29,6 +29,13 @@ func DefaultPipPackageManagerConfig() PypiPackageManagerConfig {
} }
} }
func DefaultPip3PackageManagerConfig() PypiPackageManagerConfig {
return PypiPackageManagerConfig{
InstallCommands: []string{"install"},
CommandName: "pip3",
}
}
func DefaultUvPackageManagerConfig() PypiPackageManagerConfig { func DefaultUvPackageManagerConfig() PypiPackageManagerConfig {
return PypiPackageManagerConfig{ return PypiPackageManagerConfig{
InstallCommands: []string{"add", "install"}, InstallCommands: []string{"add", "install"},
@@ -58,6 +65,8 @@ func NewPypiPackageManager(config PypiPackageManagerConfig) (*pypiPackageManager
parser = NewUVCommandParser(config) parser = NewUVCommandParser(config)
case "poetry": case "poetry":
parser = NewPoetryCommandParser(config) parser = NewPoetryCommandParser(config)
case "pip3":
parser = NewPipCommandParser(config)
default: default:
return nil, fmt.Errorf("unsupported package manager: %s", config.CommandName) return nil, fmt.Errorf("unsupported package manager: %s", config.CommandName)
} }