How I Replaced 12 Python Packages with Just the Standard Library
My "Write-Up" Submission for the Zero Dependency Hackathon 2026
🚀 Introduction
Every Python developer knows the feeling. You want to scan your code for security vulnerabilities, so you reach for a collection of packages:
pip install bandit pylint flake8 safety requests colorama click pydantic loguru jinja2 python-dotenv tqdm
That's 12 packages with a combined 200+ million weekly downloads.
But installing these tools can also introduce a growing dependency tree.
For example, bandit can bring in packages such as
pbr, stevedore, and PyYAML.
Similarly, pylint relies on packages such as
astroid, isort, and tomlkit.
Before long, a simple security or code-quality setup can become a collection of dependencies that needs to be installed, maintained, and kept compatible.
And sometimes, the setup itself becomes enough of a barrier that developers simply don't use these tools.
So I asked myself: What if I could build a security scanner that doesn't install ANY packages?
🎯 What I Actually Built
The result was Code Auditor Pro — a zero-dependency security scanner designed to analyze source code and identify common security, quality, and style issues.
The scanner looks for:
- 🔴 Hardcoded Secrets — Passwords, API keys, and tokens
- 🔴 SQL Injection — Unsafe database queries
- 🔴 Dangerous Functions —
eval,exec,os.system,pickle.load - 🟡 Bad Practices — Bare
except:and missing docstrings - 🔵 Style Issues — Lines longer than 100 characters and bad indentation
One file. 800+ lines. 0 dependencies. 100% standard library.
📦 The Packages I Replaced with the Standard Library
Instead of depending on external packages, I explored what Python already provides through its standard library.
1. bandit / pylint / flake8 — Security & Code Analysis
The Packages:
I would normally use bandit for security scanning,
pylint for code quality, and flake8 for style checking.
My Replacement: ast + re
import ast
import re
# AST (Abstract Syntax Tree) parses code structure
tree = ast.parse(code)
# I walk through every node in the tree
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
complexity = self._calculate_complexity(node)
if complexity > 10:
# Add issue: "Function has high complexity"
if isinstance(node, ast.ExceptHandler) and node.type is None:
# Add issue: "Bare except detected"
Why it works:
Python's built-in ast module gives me direct access to the
structure of Python source code. It allows me to inspect functions, classes,
imports, exceptions, and other syntax elements.
The re module complements this by handling pattern matching
for things such as hardcoded secrets and suspicious functions.
2. click / typer — Command-Line Interface
The Package:
I would normally use click or typer to create
command-line interfaces.
My Replacement: argparse
import argparse
parser = argparse.ArgumentParser(description="Code Auditor Pro")
parser.add_argument('--port',
type=int,
default=8080,
help='Port to run on')
parser.add_argument('--scan',
help='Scan a file')
parser.add_argument('--compare',
nargs=2,
help='Compare two files')
parser.add_argument('--export',
help='Export report')
args = parser.parse_args()
Why it works:
argparse is part of Python's standard library and provides
argument parsing, flags, help messages, and command-line options without
requiring another installation.
3. loguru — Logging
The Package:
I would normally use loguru for structured and convenient logging.
My Replacement: logging
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
Why it works:
Python's built-in logging module supports log levels,
handlers, formatters, and configurable logging behavior.
4. colorama — Terminal Colors
The Package:
I would normally use colorama for colored terminal output.
My Replacement: ANSI escape codes
COLORS = {
'GREEN': '\033[92m',
'RED': '\033[91m',
'YELLOW': '\033[93m',
'BLUE': '\033[94m',
'CYAN': '\033[96m',
'RESET': '\033[0m',
'BOLD': '\033[1m'
}
def color_text(text, color):
return f"{COLORS.get(color, '')}{text}{COLORS['RESET']}"
Why it works: ANSI escape sequences can be used to control terminal text formatting without installing an additional package.
5. pydantic — Data Validation
The Package:
I would normally use pydantic for data validation and
structured models.
My Replacement: dataclasses
from dataclasses import dataclass
@dataclass
class CodeIssue:
file: str
line: int
severity: str
category: str
message: str
code: str
fix: str = ""
language: str = "python"
Why it works:
dataclasses provide a clean way to represent structured data
using Python's own standard library.
6. requests — HTTP Server
The Package:
I would normally use requests for HTTP communication.
My Replacement:
http.server + socketserver
import http.server
import socketserver
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header(
'Content-type',
'text/html'
)
self.end_headers()
self.wfile.write(
HTML_TEMPLATE.encode()
)
def do_POST(self):
if self.path == '/api/scan-code':
# Process scan request
# Return JSON response
Why it works:
Python's http.server provides the basic functionality needed
to serve HTML and handle HTTP requests without introducing a web framework.
7. jinja2 — HTML Templates
The Package:
I would normally use jinja2 for HTML templating.
My Replacement: F-strings
HTML_TEMPLATE = f"""
<!DOCTYPE html>
<html>
<head>
<title>Code Auditor Pro</title>
<style>
/* CSS here */
</style>
</head>
<body>
<h1>🔍 Code Auditor</h1>
<div>Score: {score}</div>
<div>Grade: {grade}</div>
</body>
</html>
"""
Why it works: For a relatively simple application, Python f-strings are enough to generate dynamic HTML without introducing a separate template engine.
8. python-dotenv — Configuration
The Package:
I would normally use python-dotenv for configuration
through environment files.
My Replacement:
json + os.environ
import json
import os
# Load config from JSON file
with open('config.json', 'r') as f:
config = json.load(f)
# Or read environment variables directly
port = int(
os.environ.get('PORT', 8080)
)
debug = (
os.environ.get('DEBUG', 'false')
.lower() == 'true'
)
Why it works: Python already provides tools for reading JSON configuration files and environment variables.
9. tqdm — Progress Bars
The Package:
I would normally use tqdm for progress indicators.
My Replacement:
Manual progress using sys.stdout
import sys
import time
def show_progress(current, total):
bar_width = 50
progress = int(
bar_width * current / total
)
bar = (
'█' * progress
+ '░' * (bar_width - progress)
)
sys.stdout.write(
f'\r[{bar}] {current}/{total}'
)
sys.stdout.flush()
Why it works: A progress bar can be implemented using normal terminal output and carriage returns. No dedicated progress-bar package is required.
10. pathlib — File Path Handling
The Package:
I initially considered pathlib as something that needed
replacement.
The Discovery:
pathlib is already part of Python's standard library.
from pathlib import Path
import os
# Path handling with built-in pathlib
project_path = Path(
"D:/zerodependency/code-auditor"
)
for py_file in project_path.rglob("*.py"):
print(f"Found: {py_file}")
Why it works:
pathlib has been part of Python's standard library since
Python 3.4. This means there is no third-party dependency to install.
💀 The Hardest Parts of Building Without Dependencies
1. AST Is Powerful, But Limited
Python's built-in ast module gives me detailed information
about source-code structure. However, static analysis still has limitations.
- What is actually imported versus what is only referenced
- Runtime behavior versus static code structure
- Different ways of importing the same functionality
Because of this, I had to build my own import-resolution logic rather than relying on an existing package.
2. The "Eval" Problem
Regex is useful, but it can also create false positives.
For example, cursor.execute(query) contains the letters
exec, even though it is not a call to Python's
exec() function.
# Had to add this check:
if 'cursor.execute' in line and func == 'exec':
continue # This is not actually exec()
I had to carefully refine the detection logic so that the scanner could distinguish between genuinely dangerous functions and harmless text.
3. Finding Secrets Without a Pattern Database
Hardcoded secrets can often be detected using regular expressions. The challenge was creating the detection patterns myself.
secrets = [
(
r'password\s*=\s*["\']([^"\']+)["\']',
"CRITICAL",
"Hardcoded password"
),
(
r'api_key\s*=\s*["\']([^"\']+)["\']',
"CRITICAL",
"Hardcoded API key"
),
(
r'sk-\w{20,}',
"CRITICAL",
"OpenAI API Key"
),
(
r'ghp_\w{20,}',
"CRITICAL",
"GitHub Token"
),
(
r'AIzaSy\w{20,}',
"CRITICAL",
"Google API Key"
),
(
r'AWS[A-Z0-9]{16,}',
"CRITICAL",
"AWS Access Key"
),
# ... 15+ patterns total
]
4. Cross-Platform Issues
One of the hackathon requirements was that the project should build using a single command.
The command worked locally:
py -3.12 main.py
But testing on different systems exposed additional problems. File paths, ports, and Python versions could behave differently.
I had to:
- Add
allow_reuse_address = Trueto the server - Support a configurable
--portargument - Handle cross-platform file paths
- Ensure Python 3.8+ compatibility
No dependency would have solved these problems. Testing on different systems was the real solution.
5. Replacing the HTML Template
I wanted the application to have a useful and attractive interface. Normally, I might reach for React or Jinja2.
Instead, everything had to be created using pure strings:
HTML_TEMPLATE = """<!DOCTYPE html>
<html>
<head>
<style>
/* 150 lines of pure CSS */
</style>
</head>
<body>
<!-- 100 lines of HTML -->
<script>
// 200 lines of vanilla JavaScript
</script>
</body>
</html>
"""
No frameworks. No CDNs. No external assets. Just one file containing everything.
6. Multi-Language Support
I also added support for JavaScript and Go. This meant creating language-specific scanning logic.
def scan_javascript(self, code, filename):
# JavaScript patterns
# 'const password = "admin123"' → detected!
# 'eval(cmd)' → detected!
def scan_go(self, code, filename):
# Go patterns
# 'password := "admin123"' → detected!
# 'unsafe.' → detected!
7. Language-Specific Fix Suggestions
Detecting an issue is only half the job. The suggested fix should also make sense for the language being scanned.
# Python fix:
import os
PASSWORD = os.environ.get("PASSWORD")
# JavaScript fix:
const PASSWORD = process.env.PASSWORD;
# Go fix:
import "os"
password := os.Getenv("PASSWORD")
📊 The Cost of Replacing These Packages
Building these replacements took time, but it also showed me how much functionality can be achieved using Python's built-in capabilities.
| Package | Replacement | Time Spent |
|---|---|---|
bandit / pylint / flake8 |
ast + re |
6 hours |
click |
argparse |
1 hour |
loguru |
logging |
30 minutes |
colorama |
ANSI codes | 30 minutes |
pydantic |
dataclasses |
1 hour |
requests |
http.server |
2 hours |
jinja2 |
F-strings | 1 hour |
python-dotenv |
json + os.environ |
30 minutes |
tqdm |
Manual sys.stdout |
1 hour |
pathlib |
Built-in pathlib |
0 |
| Total | 10+ substitutions | ~13.5 hours |
🏆 The Reward
The project achieved the full 16/16 bonus points.
| Bonus | Points | How I Got It |
|---|---|---|
| Single File | +5 | Everything in main.py |
| Reproducible Build | +5 | Pure Python, deterministic output |
| Package Killer | +3 | Replaces bandit, pylint, and flake8 |
| STDLIB Log | +3 | 10+ standard-library substitutions documented |
🎯 Key Takeaways
What I Learned
- The standard library is more powerful than you think. Python already provides a surprising amount of functionality.
- Dependencies are a convenience, not always a necessity. Understanding the standard library can help you build many tools without additional packages.
- Cross-platform issues are real. Testing on different systems is essential.
- Writing your own code gives you control. You understand what is happening underneath the abstractions.
- The "one command" rule is harder than it looks. Making a project reliable across environments takes real effort.
Why This Matters
- Security: Fewer third-party dependencies can reduce dependency-related attack surface.
- Speed: No dependency installation step.
- Portability: The project can run wherever the required Python version is available.
- Reliability: Fewer dependency conflicts and version constraints.
- Learning: You gain a better understanding of what libraries are doing underneath.
📊 Project Stats
Languages: Python, JavaScript, Go
Time Spent: ~13.5 hours
💡 Final Thoughts
Building a security scanner without any packages felt like the most unnatural thing I've ever done.
Every time I needed to implement something, my muscle memory wanted to
reach for pip install.
But forcing myself to work with only the standard library changed the way I think about Python. Instead of immediately searching for a package, I started asking what Python itself could already do.
The hackathon rule of having an empty dependency manifest wasn't simply a restriction. It became an opportunity to understand the tools and abstractions I normally take for granted.
Building without dependencies forces you to ask: "How does this actually work?"
And the answer is:
It works differently than you expected.
🙏 Special Thanks
- My Team: Diya Karmakar, Krishna Popat, Shyam Makvana, Saurabh Singh Rathore
"Every dependency is a stranger. This time, invite none."
— Zero Dependency Hackathon Motto
Comments
Post a Comment