package python import ( "fmt" "os" "path/filepath" "strings" ) type Python struct { // Add fields as needed for Python analysis } // Name returns the name of the Python technology stack. func (p *Python) Name() string { return "Python" } // Analyze analyzes the codebase and returns a guessed technology stack for Python. func (p *Python) Analyze(codebasePath string) (bool, error) { // Verify if the codebasePath is valid if codebasePath == "" { return false, fmt.Errorf("codebase path is empty") } // Check if codebase exists if _, err := os.Stat(codebasePath); os.IsNotExist(err) { return false, fmt.Errorf("codebase path does not exist: %s", codebasePath) } // Placeholder: Check for a common Python file like requirements.txt or a .py file // This is a very basic check and should be expanded. foundPythonIndicator := false if _, err := os.Stat(filepath.Join(codebasePath, "requirements.txt")); err == nil { foundPythonIndicator = true } // You might want to walk the directory for .py files if requirements.txt isn't found if !foundPythonIndicator { walkErr := filepath.WalkDir(codebasePath, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() && strings.HasSuffix(d.Name(), ".py") { foundPythonIndicator = true return filepath.SkipDir // Found a .py file, no need to search further in this dir for this check } return nil }) if walkErr != nil { // Log or handle walk error, but it might not be fatal for analysis fmt.Printf("Error walking directory for python files: %v\n", walkErr) } } return foundPythonIndicator, nil } type PythonProjectAnalysis struct { PythonVersion string `json:"python_version"` Framework string `json:"framework"` UsePoetry bool `json:"use_poetry"` UsePipenv bool `json:"use_pipenv"` Entrypoint string `json:"entrypoint"` Port int `json:"port"` Packages []string `json:"packages"` SystemDeps []string `json:"system_deps"` StartCommand string `json:"start_command"` } func (p *Python) analyzePythonProject(codebasePath string) (*PythonProjectAnalysis, error) { analysis := &PythonProjectAnalysis{ Port: 8000, // Default for Python } // Check for Poetry pyprojectPath := filepath.Join(codebasePath, "pyproject.toml") if p.fileExists(pyprojectPath) { analysis.UsePoetry = true // Could parse pyproject.toml for more details } // Check for Pipenv pipfilePath := filepath.Join(codebasePath, "Pipfile") if p.fileExists(pipfilePath) { analysis.UsePipenv = true } // Read requirements.txt if it exists reqPath := filepath.Join(codebasePath, "requirements.txt") if content, err := os.ReadFile(reqPath); err == nil { lines := strings.Split(string(content), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line != "" && !strings.HasPrefix(line, "#") { // Extract package name (before == or >=) parts := strings.FieldsFunc(line, func(r rune) bool { return r == '=' || r == '>' || r == '<' || r == '!' || r == '~' }) if len(parts) > 0 { analysis.Packages = append(analysis.Packages, parts[0]) } } } } // Detect framework from packages for _, pkg := range analysis.Packages { switch { case strings.Contains(pkg, "fastapi"): analysis.Framework = "fastapi" case strings.Contains(pkg, "flask"): analysis.Framework = "flask" case strings.Contains(pkg, "django"): analysis.Framework = "django" case strings.Contains(pkg, "tornado"): analysis.Framework = "tornado" case strings.Contains(pkg, "sanic"): analysis.Framework = "sanic" } } // Detect entrypoint entrypoints := []string{"main.py", "app.py", "server.py", "run.py", "wsgi.py"} for _, ep := range entrypoints { if p.fileExists(filepath.Join(codebasePath, ep)) { analysis.Entrypoint = ep break } } if analysis.Entrypoint == "" { if analysis.Framework == "django" && p.fileExists(filepath.Join(codebasePath, "manage.py")) { analysis.Entrypoint = "manage.py" } else { analysis.Entrypoint = "app.py" // Default } } // Check for system dependencies for _, pkg := range analysis.Packages { switch { case strings.Contains(pkg, "psycopg2") || strings.Contains(pkg, "pg"): analysis.SystemDeps = append(analysis.SystemDeps, "libpq-dev") case strings.Contains(pkg, "mysql"): analysis.SystemDeps = append(analysis.SystemDeps, "default-libmysqlclient-dev") case strings.Contains(pkg, "pillow") || strings.Contains(pkg, "PIL"): analysis.SystemDeps = append(analysis.SystemDeps, "libjpeg-dev", "zlib1g-dev") case strings.Contains(pkg, "lxml"): analysis.SystemDeps = append(analysis.SystemDeps, "libxml2-dev", "libxslt1-dev") } } // Determine start command switch analysis.Framework { case "fastapi": analysis.StartCommand = fmt.Sprintf("uvicorn %s:app --host 0.0.0.0 --port %d", strings.TrimSuffix(analysis.Entrypoint, ".py"), analysis.Port) case "flask": analysis.StartCommand = fmt.Sprintf("flask run --host=0.0.0.0 --port=%d", analysis.Port) case "django": analysis.StartCommand = fmt.Sprintf("python manage.py runserver 0.0.0.0:%d", analysis.Port) default: analysis.StartCommand = fmt.Sprintf("python %s", analysis.Entrypoint) } return analysis, nil } func (p *Python) fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // GenerateDockerfile generates a Dockerfile for Python projects based on analysis func (p *Python) GenerateDockerfile(codebasePath string) (string, error) { // Analyze the Python project analysis, err := p.analyzePythonProject(codebasePath) if err != nil { return "", fmt.Errorf("failed to analyze Python project: %w", err) } pythonVersion := analysis.PythonVersion if pythonVersion == "" { pythonVersion = "3.11" } // Determine dependency management approach var copyDeps, installDeps string if analysis.UsePoetry { copyDeps = `COPY pyproject.toml poetry.lock ./` installDeps = `RUN pip install poetry && \ poetry config virtualenvs.create false && \ poetry install --only=main` } else if analysis.UsePipenv { copyDeps = `COPY Pipfile Pipfile.lock ./` installDeps = `RUN pip install pipenv && \ pipenv install --system --deploy` } else { copyDeps = `COPY requirements.txt ./` installDeps = `RUN pip install --no-cache-dir -r requirements.txt` } // System dependencies systemDepsInstall := "" if len(analysis.SystemDeps) > 0 { systemDepsInstall = fmt.Sprintf(`RUN apt-get update && \ apt-get install -y %s && \ rm -rf /var/lib/apt/lists/*`, strings.Join(analysis.SystemDeps, " ")) } dockerfile := fmt.Sprintf(`# Auto-generated Dockerfile for Python application # Generated by BYOP Engine - Python Stack Analyzer FROM python:%s-slim # Set working directory WORKDIR /app # Install system dependencies %s # Upgrade pip RUN pip install --upgrade pip # Copy dependency files %s # Install Python dependencies %s # Copy source code COPY . . # Create non-root user RUN useradd --create-home --shell /bin/bash app && \ chown -R app:app /app USER app # Expose port EXPOSE %d # Start the application CMD ["%s"] `, pythonVersion, systemDepsInstall, copyDeps, installDeps, analysis.Port, analysis.StartCommand, ) return dockerfile, nil }