Modules and Packages
Introduction
As your Python programs grow larger, organizing code becomes essential. Modules allow you to split your code into separate files, making it more manageable, reusable, and testable.
A module is simply a Python file containing functions, classes, and variables. A package is a collection of modules organized in directories with special __init__.py files.
Python's module system provides:
- Code organization and reusability
- Namespace management
- Import control and visibility
- Standard library access
- Third-party package integration
Understanding modules is crucial for building scalable Python applications and leveraging the vast Python ecosystem.
Concepts
Creating Modules
- Any
.pyfile is a module - Module name is the filename without
.py - Use
importto access module contents
Import Statements
import module_name- imports entire modulefrom module_name import function_name- imports specific itemsfrom module_name import *- imports all (not recommended)import module_name as alias- creates an alias
Packages
- Directories containing
__init__.pyfile - Can contain sub-packages and modules
- Create hierarchical organization
__init__.pycontrols package initialization
Module Search Path
- Python looks for modules in specific locations
- Current directory, PYTHONPATH, standard library, site-packages
- Use
sys.pathto see search paths
Examples
Basic Module Creation
# math_utils.py
"""A module for mathematical utility functions"""
PI = 3.14159
GOLDEN_RATIO = 1.61803
def add(a, b):
"""Add two numbers"""
return a + b
def multiply(a, b):
"""Multiply two numbers"""
return a * b
def circle_area(radius):
"""Calculate circle area"""
return PI * radius * radius
class Calculator:
"""A simple calculator class"""
def __init__(self):
self.history = []
def calculate(self, operation, a, b):
if operation == "add":
result = add(a, b)
elif operation == "multiply":
result = multiply(a, b)
else:
raise ValueError(f"Unknown operation: {operation}")
self.history.append(f"{operation}({a}, {b}) = {result}")
return result
if __name__ == "__main__":
# Code that runs only when module is executed directly
print("Math utils module loaded")
print(f"PI = {PI}")
calc = Calculator()
print(f"2 + 3 = {calc.calculate('add', 2, 3)}")
Using the Module
# main.py
import math_utils
# Access module attributes
print(f"PI value: {math_utils.PI}")
# Use module functions
result = math_utils.add(5, 3)
print(f"5 + 3 = {result}")
# Use module classes
calculator = math_utils.Calculator()
area = math_utils.circle_area(10)
print(f"Circle area (r=10): {area}")
# Different import styles
from math_utils import multiply, GOLDEN_RATIO
print(f"4 * 7 = {multiply(4, 7)}")
print(f"Golden ratio: {GOLDEN_RATIO}")
# Import with alias
import math_utils as math
print(f"Using alias: {math.add(1, 2)}")
# Import specific class
from math_utils import Calculator
my_calc = Calculator()
print(f"Calculator result: {my_calc.calculate('multiply', 6, 7)}")
Package Structure
my_package/
__init__.py
core/
__init__.py
database.py
models.py
utils/
__init__.py
helpers.py
validators.py
tests/
__init__.py
test_core.py
test_utils.py
Package Example - Database Module
# my_package/core/database.py
"""Database connection and operations"""
import json
from typing import Dict, List, Any
class DatabaseConnection:
"""Mock database connection"""
def __init__(self, connection_string: str):
self.connection_string = connection_string
self.is_connected = False
self.data = {}
def connect(self):
"""Establish database connection"""
print(f"Connecting to: {self.connection_string}")
self.is_connected = True
return True
def disconnect(self):
"""Close database connection"""
self.is_connected = False
print("Database connection closed")
def insert(self, table: str, data: Dict[str, Any]):
"""Insert data into table"""
if not self.is_connected:
raise ConnectionError("Not connected to database")
if table not in self.data:
self.data[table] = []
# Add ID if not present
if 'id' not in data:
data['id'] = len(self.data[table]) + 1
self.data[table].append(data)
return data['id']
def select(self, table: str, where: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""Select data from table"""
if not self.is_connected:
raise ConnectionError("Not connected to database")
if table not in self.data:
return []
results = self.data[table]
if where:
filtered_results = []
for row in results:
match = True
for key, value in where.items():
if key not in row or row[key] != value:
match = False
break
if match:
filtered_results.append(row)
return filtered_results
return results.copy()
def create_connection(db_type: str = "sqlite", **kwargs) -> DatabaseConnection:
"""Factory function to create database connections"""
if db_type == "sqlite":
conn_str = kwargs.get("database", "memory.db")
elif db_type == "postgresql":
host = kwargs.get("host", "localhost")
port = kwargs.get("port", 5432)
database = kwargs.get("database", "mydb")
conn_str = f"postgresql://{host}:{port}/{database}"
else:
raise ValueError(f"Unsupported database type: {db_type}")
return DatabaseConnection(conn_str)
Package Example - Models Module
# my_package/core/models.py
"""Data models for the application"""
from datetime import datetime
from typing import Optional, List, Dict, Any
from dataclasses import dataclass, field
@dataclass
class User:
"""User model"""
username: str
email: str
first_name: str = ""
last_name: str = ""
is_active: bool = True
created_at: datetime = field(default_factory=datetime.now)
id: Optional[int] = None
def full_name(self) -> str:
"""Get user's full name"""
return f"{self.first_name} {self.last_name}".strip()
def to_dict(self) -> Dict[str, Any]:
"""Convert user to dictionary"""
return {
'id': self.id,
'username': self.username,
'email': self.email,
'first_name': self.first_name,
'last_name': self.last_name,
'full_name': self.full_name(),
'is_active': self.is_active,
'created_at': self.created_at.isoformat()
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'User':
"""Create user from dictionary"""
# Handle datetime conversion
if isinstance(data.get('created_at'), str):
data['created_at'] = datetime.fromisoformat(data['created_at'])
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
@dataclass
class Product:
"""Product model"""
name: str
price: float
category: str = "general"
description: str = ""
in_stock: bool = True
id: Optional[int] = None
created_at: datetime = field(default_factory=datetime.now)
def formatted_price(self) -> str:
"""Get formatted price string"""
return f"${self.price:.2f}"
def to_dict(self) -> Dict[str, Any]:
"""Convert product to dictionary"""
return {
'id': self.id,
'name': self.name,
'price': self.price,
'formatted_price': self.formatted_price(),
'category': self.category,
'description': self.description,
'in_stock': self.in_stock,
'created_at': self.created_at.isoformat()
}
class UserRepository:
"""Repository for user operations"""
def __init__(self, db_connection):
self.db = db_connection
self.table_name = "users"
def create(self, user: User) -> User:
"""Create a new user"""
user_data = user.to_dict()
user_data.pop('id', None) # Remove ID for creation
user_id = self.db.insert(self.table_name, user_data)
user.id = user_id
return user
def find_by_username(self, username: str) -> Optional[User]:
"""Find user by username"""
results = self.db.select(self.table_name, {"username": username})
if results:
return User.from_dict(results[0])
return None
def find_all(self) -> List[User]:
"""Get all users"""
results = self.db.select(self.table_name)
return [User.from_dict(row) for row in results]
Package Initialization
# my_package/__init__.py
"""My Package - A demonstration of Python packages"""
__version__ = "1.0.0"
__author__ = "Your Name"
# Import key components for easy access
from .core.database import create_connection
from .core.models import User, Product, UserRepository
# Package-level constants
DEFAULT_DATABASE_TYPE = "sqlite"
# Package-level functions
def get_version():
"""Get package version"""
return __version__
def initialize_database(db_type=None):
"""Initialize database with default settings"""
db_type = db_type or DEFAULT_DATABASE_TYPE
conn = create_connection(db_type)
conn.connect()
return conn
# Control what gets imported with "from my_package import *"
__all__ = [
'create_connection',
'User',
'Product',
'UserRepository',
'get_version',
'initialize_database'
]
Using the Package
# app.py
"""Application using our custom package"""
# Different ways to import from packages
import my_package
from my_package import User, initialize_database
from my_package.core.database import DatabaseConnection
from my_package.core import models
def main():
# Using package-level imports
print(f"Package version: {my_package.get_version()}")
# Initialize database
db = initialize_database("sqlite")
# Create repository
user_repo = my_package.UserRepository(db)
# Create users
user1 = User(
username="alice",
email="alice@example.com",
first_name="Alice",
last_name="Johnson"
)
user2 = models.User(
username="bob",
email="bob@example.com",
first_name="Bob",
last_name="Smith"
)
# Save users
saved_user1 = user_repo.create(user1)
saved_user2 = user_repo.create(user2)
print(f"Created user: {saved_user1.full_name()} (ID: {saved_user1.id})")
print(f"Created user: {saved_user2.full_name()} (ID: {saved_user2.id})")
# Find users
found_user = user_repo.find_by_username("alice")
if found_user:
print(f"Found user: {found_user.to_dict()}")
# List all users
all_users = user_repo.find_all()
print(f"Total users: {len(all_users)}")
# Clean up
db.disconnect()
if __name__ == "__main__":
main()
Challenges
1. File Operations Module
Create a module file_manager.py with functions for reading, writing, and managing files.
💡 Show Answer
# file_manager.py
"""File management utilities module"""
import os
import json
import shutil
from pathlib import Path
from typing import List, Dict, Any, Optional
def read_text_file(filepath: str) -> str:
"""Read entire text file"""
try:
with open(filepath, 'r', encoding='utf-8') as file:
return file.read()
except FileNotFoundError:
raise FileNotFoundError(f"File not found: {filepath}")
except Exception as e:
raise IOError(f"Error reading file {filepath}: {e}")
def write_text_file(filepath: str, content: str, append: bool = False) -> bool:
"""Write content to text file"""
try:
mode = 'a' if append else 'w'
# Ensure directory exists
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
with open(filepath, mode, encoding='utf-8') as file:
file.write(content)
return True
except Exception as e:
raise IOError(f"Error writing file {filepath}: {e}")
def read_json_file(filepath: str) -> Dict[str, Any]:
"""Read and parse JSON file"""
try:
with open(filepath, 'r', encoding='utf-8') as file:
return json.load(file)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in {filepath}: {e}")
except FileNotFoundError:
raise FileNotFoundError(f"JSON file not found: {filepath}")
def write_json_file(filepath: str, data: Dict[str, Any], indent: int = 2) -> bool:
"""Write data to JSON file"""
try:
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as file:
json.dump(data, file, indent=indent, ensure_ascii=False)
return True
except Exception as e:
raise IOError(f"Error writing JSON file {filepath}: {e}")
def list_files(directory: str, extension: str = None, recursive: bool = False) -> List[str]:
"""List files in directory"""
try:
path = Path(directory)
if not path.exists():
raise FileNotFoundError(f"Directory not found: {directory}")
pattern = "**/*" if recursive else "*"
if extension:
pattern += f".{extension.lstrip('.')}"
files = [str(f) for f in path.glob(pattern) if f.is_file()]
return sorted(files)
except Exception as e:
raise IOError(f"Error listing files in {directory}: {e}")
def copy_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""Copy file from source to destination"""
try:
if not overwrite and Path(destination).exists():
raise FileExistsError(f"Destination file already exists: {destination}")
# Ensure destination directory exists
Path(destination).parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
return True
except Exception as e:
raise IOError(f"Error copying file from {source} to {destination}: {e}")
def delete_file(filepath: str, silent: bool = False) -> bool:
"""Delete file"""
try:
Path(filepath).unlink()
return True
except FileNotFoundError:
if not silent:
raise FileNotFoundError(f"File not found: {filepath}")
return False
except Exception as e:
raise IOError(f"Error deleting file {filepath}: {e}")
def get_file_info(filepath: str) -> Dict[str, Any]:
"""Get file information"""
try:
path = Path(filepath)
if not path.exists():
raise FileNotFoundError(f"File not found: {filepath}")
stat = path.stat()
return {
'name': path.name,
'size_bytes': stat.st_size,
'size_mb': round(stat.st_size / 1024 / 1024, 2),
'created': stat.st_ctime,
'modified': stat.st_mtime,
'is_file': path.is_file(),
'is_directory': path.is_dir(),
'extension': path.suffix,
'parent': str(path.parent)
}
except Exception as e:
raise IOError(f"Error getting file info for {filepath}: {e}")
class FileManager:
"""File manager class for organized file operations"""
def __init__(self, base_directory: str = "."):
self.base_directory = Path(base_directory)
self.base_directory.mkdir(parents=True, exist_ok=True)
def get_path(self, filename: str) -> str:
"""Get full path for filename"""
return str(self.base_directory / filename)
def save_data(self, filename: str, data: Any, format: str = "json") -> bool:
"""Save data in specified format"""
filepath = self.get_path(filename)
if format.lower() == "json":
return write_json_file(filepath, data)
elif format.lower() == "txt":
return write_text_file(filepath, str(data))
else:
raise ValueError(f"Unsupported format: {format}")
def load_data(self, filename: str, format: str = "json") -> Any:
"""Load data from file"""
filepath = self.get_path(filename)
if format.lower() == "json":
return read_json_file(filepath)
elif format.lower() == "txt":
return read_text_file(filepath)
else:
raise ValueError(f"Unsupported format: {format}")
def cleanup(self, pattern: str = "*") -> int:
"""Clean up files matching pattern"""
files = list(self.base_directory.glob(pattern))
count = 0
for file_path in files:
if file_path.is_file():
file_path.unlink()
count += 1
return count
# Usage example
if __name__ == "__main__":
# Test the module
print("Testing file_manager module...")
# Create test data
test_data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
],
"timestamp": "2023-01-01T12:00:00"
}
# Test file operations
fm = FileManager("test_files")
# Save and load JSON
fm.save_data("users.json", test_data)
loaded_data = fm.load_data("users.json")
print(f"Loaded data: {loaded_data}")
# Save and load text
fm.save_data("readme.txt", "This is a test file", format="txt")
text_content = fm.load_data("readme.txt", format="txt")
print(f"Text content: {text_content}")
# List files
files = list_files("test_files")
print(f"Files in test_files: {files}")
# Get file info
for file_path in files:
info = get_file_info(file_path)
print(f"File info for {info['name']}: {info['size_bytes']} bytes")
# Cleanup
count = fm.cleanup()
print(f"Cleaned up {count} files")
# test_file_manager.py - Using the module
from file_manager import FileManager, read_json_file, write_json_file
def test_file_operations():
# Test basic file operations
data = {"test": True, "value": 42}
# Write and read JSON
write_json_file("test.json", data)
loaded = read_json_file("test.json")
assert loaded == data
print("✅ JSON operations work correctly")
# Test FileManager class
fm = FileManager("temp")
fm.save_data("config.json", {"debug": True, "port": 8080})
config = fm.load_data("config.json")
print(f"✅ FileManager works: {config}")
# Cleanup
fm.cleanup("*.json")
if __name__ == "__main__":
test_file_operations()
2. Configuration Package
Create a package config with modules for different configuration types (database, logging, API settings).
💡 Show Answer
# config/__init__.py
"""Configuration package for application settings"""
import os
from typing import Dict, Any
# Package version
__version__ = "1.0.0"
# Import all configuration modules
from .database import DatabaseConfig, get_database_config
from .logging_config import LoggingConfig, setup_logging
from .api import APIConfig, get_api_config
from .base import BaseConfig, Environment
# Default environment
DEFAULT_ENVIRONMENT = Environment.DEVELOPMENT
def load_all_configs(env: Environment = None) -> Dict[str, Any]:
"""Load all configuration objects"""
env = env or DEFAULT_ENVIRONMENT
return {
'database': get_database_config(env),
'logging': LoggingConfig(env),
'api': get_api_config(env),
'environment': env.value
}
def setup_application(env: Environment = None):
"""Setup application with all configurations"""
configs = load_all_configs(env)
# Setup logging first
setup_logging(configs['logging'])
print(f"Application configured for {configs['environment']}")
return configs
# Export main items
__all__ = [
'DatabaseConfig', 'LoggingConfig', 'APIConfig', 'BaseConfig',
'Environment', 'get_database_config', 'get_api_config',
'setup_logging', 'load_all_configs', 'setup_application'
]
# config/base.py
"""Base configuration classes and utilities"""
import os
from enum import Enum
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
class Environment(Enum):
"""Application environments"""
DEVELOPMENT = "development"
TESTING = "testing"
STAGING = "staging"
PRODUCTION = "production"
class BaseConfig(ABC):
"""Base configuration class"""
def __init__(self, environment: Environment = Environment.DEVELOPMENT):
self.environment = environment
self.load_from_environment()
@abstractmethod
def load_from_environment(self):
"""Load configuration from environment variables"""
pass
def get_env_var(self, key: str, default: Any = None, required: bool = False) -> str:
"""Get environment variable with validation"""
value = os.getenv(key, default)
if required and value is None:
raise ValueError(f"Required environment variable {key} is not set")
return value
def get_env_bool(self, key: str, default: bool = False) -> bool:
"""Get boolean environment variable"""
value = self.get_env_var(key, str(default))
return value.lower() in ('true', '1', 'yes', 'on')
def get_env_int(self, key: str, default: int = 0) -> int:
"""Get integer environment variable"""
value = self.get_env_var(key, str(default))
try:
return int(value)
except ValueError:
raise ValueError(f"Environment variable {key} must be an integer")
def to_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary"""
return {
key: value for key, value in self.__dict__.items()
if not key.startswith('_')
}
def __str__(self):
items = [f"{k}={v}" for k, v in self.to_dict().items()]
return f"{self.__class__.__name__}({', '.join(items)})"
# config/database.py
"""Database configuration module"""
from typing import Optional
from .base import BaseConfig, Environment
class DatabaseConfig(BaseConfig):
"""Database configuration"""
def __init__(self, environment: Environment = Environment.DEVELOPMENT):
# Default values
self.host = "localhost"
self.port = 5432
self.database = "myapp"
self.username = "user"
self.password = "password"
self.pool_size = 5
self.ssl_mode = "prefer"
self.timeout = 30
super().__init__(environment)
def load_from_environment(self):
"""Load database config from environment variables"""
self.host = self.get_env_var("DB_HOST", self.host)
self.port = self.get_env_int("DB_PORT", self.port)
self.database = self.get_env_var("DB_NAME", self.database)
self.username = self.get_env_var("DB_USER", self.username)
self.password = self.get_env_var("DB_PASSWORD", self.password)
self.pool_size = self.get_env_int("DB_POOL_SIZE", self.pool_size)
self.ssl_mode = self.get_env_var("DB_SSL_MODE", self.ssl_mode)
self.timeout = self.get_env_int("DB_TIMEOUT", self.timeout)
# Environment-specific overrides
if self.environment == Environment.PRODUCTION:
self.ssl_mode = "require"
self.pool_size = 20
elif self.environment == Environment.TESTING:
self.database = f"{self.database}_test"
self.pool_size = 2
def get_connection_string(self) -> str:
"""Get database connection string"""
return (f"postgresql://{self.username}:{self.password}@"
f"{self.host}:{self.port}/{self.database}?"
f"sslmode={self.ssl_mode}")
def get_connection_params(self) -> dict:
"""Get connection parameters as dictionary"""
return {
'host': self.host,
'port': self.port,
'database': self.database,
'user': self.username,
'password': self.password,
'sslmode': self.ssl_mode,
'connect_timeout': self.timeout
}
def get_database_config(environment: Environment = None) -> DatabaseConfig:
"""Factory function to get database configuration"""
if environment is None:
environment = Environment.DEVELOPMENT
return DatabaseConfig(environment)
# Environment-specific configurations
class DevelopmentDatabaseConfig(DatabaseConfig):
def __init__(self):
super().__init__(Environment.DEVELOPMENT)
self.host = "localhost"
self.database = "myapp_dev"
class ProductionDatabaseConfig(DatabaseConfig):
def __init__(self):
super().__init__(Environment.PRODUCTION)
self.ssl_mode = "require"
self.pool_size = 50
# config/logging_config.py
"""Logging configuration module"""
import logging
import sys
from typing import Dict, Any
from .base import BaseConfig, Environment
class LoggingConfig(BaseConfig):
"""Logging configuration"""
def __init__(self, environment: Environment = Environment.DEVELOPMENT):
# Default values
self.level = "INFO"
self.format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
self.date_format = "%Y-%m-%d %H:%M:%S"
self.log_to_file = False
self.log_file = "app.log"
self.max_file_size = 10 * 1024 * 1024 # 10MB
self.backup_count = 5
self.log_to_console = True
super().__init__(environment)
def load_from_environment(self):
"""Load logging config from environment variables"""
self.level = self.get_env_var("LOG_LEVEL", self.level).upper()
self.format = self.get_env_var("LOG_FORMAT", self.format)
self.log_to_file = self.get_env_bool("LOG_TO_FILE", self.log_to_file)
self.log_file = self.get_env_var("LOG_FILE", self.log_file)
self.log_to_console = self.get_env_bool("LOG_TO_CONSOLE", self.log_to_console)
# Environment-specific settings
if self.environment == Environment.PRODUCTION:
self.level = "WARNING"
self.log_to_file = True
self.log_file = "/var/log/myapp/app.log"
elif self.environment == Environment.DEVELOPMENT:
self.level = "DEBUG"
self.log_to_console = True
elif self.environment == Environment.TESTING:
self.level = "ERROR"
self.log_to_console = False
def get_logging_config(self) -> Dict[str, Any]:
"""Get logging configuration dictionary"""
return {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': self.format,
'datefmt': self.date_format,
},
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(message)s',
'datefmt': self.date_format,
}
},
'handlers': self._get_handlers(),
'root': {
'level': self.level,
'handlers': self._get_handler_names(),
}
}
def _get_handlers(self) -> Dict[str, Any]:
"""Get logging handlers configuration"""
handlers = {}
if self.log_to_console:
handlers['console'] = {
'class': 'logging.StreamHandler',
'level': self.level,
'formatter': 'default',
'stream': 'ext://sys.stdout',
}
if self.log_to_file:
handlers['file'] = {
'class': 'logging.handlers.RotatingFileHandler',
'level': self.level,
'formatter': 'detailed',
'filename': self.log_file,
'maxBytes': self.max_file_size,
'backupCount': self.backup_count,
}
return handlers
def _get_handler_names(self) -> list:
"""Get list of handler names"""
names = []
if self.log_to_console:
names.append('console')
if self.log_to_file:
names.append('file')
return names
def setup_logging(config: LoggingConfig):
"""Setup logging with the provided configuration"""
import logging.config
logging_dict = config.get_logging_config()
logging.config.dictConfig(logging_dict)
# Test logging setup
logger = logging.getLogger(__name__)
logger.info(f"Logging configured for {config.environment.value} environment")
logger.debug(f"Log level: {config.level}")
# config/api.py
"""API configuration module"""
from typing import List, Dict, Any
from .base import BaseConfig, Environment
class APIConfig(BaseConfig):
"""API server configuration"""
def __init__(self, environment: Environment = Environment.DEVELOPMENT):
# Default values
self.host = "127.0.0.1"
self.port = 8000
self.debug = True
self.cors_enabled = True
self.cors_origins = ["*"]
self.rate_limit = 100 # requests per minute
self.api_key_required = False
self.api_version = "v1"
self.request_timeout = 30
self.max_content_length = 16 * 1024 * 1024 # 16MB
super().__init__(environment)
def load_from_environment(self):
"""Load API config from environment variables"""
self.host = self.get_env_var("API_HOST", self.host)
self.port = self.get_env_int("API_PORT", self.port)
self.debug = self.get_env_bool("API_DEBUG", self.debug)
self.cors_enabled = self.get_env_bool("CORS_ENABLED", self.cors_enabled)
self.rate_limit = self.get_env_int("RATE_LIMIT", self.rate_limit)
self.api_key_required = self.get_env_bool("API_KEY_REQUIRED", self.api_key_required)
self.api_version = self.get_env_var("API_VERSION", self.api_version)
# Parse CORS origins
cors_origins = self.get_env_var("CORS_ORIGINS", ",".join(self.cors_origins))
self.cors_origins = [origin.strip() for origin in cors_origins.split(",")]
# Environment-specific settings
if self.environment == Environment.PRODUCTION:
self.debug = False
self.host = "0.0.0.0"
self.api_key_required = True
self.cors_origins = self.get_production_cors_origins()
self.rate_limit = 1000
elif self.environment == Environment.TESTING:
self.port = 0 # Use random port for testing
self.debug = False
self.rate_limit = 1000
def get_production_cors_origins(self) -> List[str]:
"""Get production CORS origins"""
prod_origins = self.get_env_var("PROD_CORS_ORIGINS")
if prod_origins:
return [origin.strip() for origin in prod_origins.split(",")]
return ["https://myapp.com", "https://api.myapp.com"]
def get_server_url(self) -> str:
"""Get full server URL"""
return f"http://{self.host}:{self.port}"
def get_api_base_url(self) -> str:
"""Get API base URL with version"""
return f"{self.get_server_url()}/api/{self.api_version}"
def get_api_config(environment: Environment = None) -> APIConfig:
"""Factory function to get API configuration"""
if environment is None:
environment = Environment.DEVELOPMENT
return APIConfig(environment)
# test_config_package.py - Using the configuration package
import os
from config import (
load_all_configs,
setup_application,
Environment,
DatabaseConfig,
APIConfig,
LoggingConfig
)
def test_configuration_package():
"""Test the configuration package"""
# Test individual configs
print("Testing individual configurations:")
# Database config
db_config = DatabaseConfig(Environment.DEVELOPMENT)
print(f"Database: {db_config}")
print(f"Connection string: {db_config.get_connection_string()}")
# API config
api_config = APIConfig(Environment.DEVELOPMENT)
print(f"API: {api_config}")
print(f"Server URL: {api_config.get_server_url()}")
# Logging config
log_config = LoggingConfig(Environment.DEVELOPMENT)
print(f"Logging: {log_config}")
# Test loading all configs
print("\nTesting complete configuration loading:")
configs = load_all_configs(Environment.DEVELOPMENT)
for config_name, config_obj in configs.items():
if hasattr(config_obj, 'to_dict'):
print(f"{config_name}: {config_obj.to_dict()}")
else:
print(f"{config_name}: {config_obj}")
# Test environment variables
print("\nTesting with environment variables:")
os.environ["DB_HOST"] = "production.example.com"
os.environ["API_PORT"] = "9000"
os.environ["LOG_LEVEL"] = "ERROR"
env_configs = load_all_configs(Environment.PRODUCTION)
print(f"Production DB host: {env_configs['database'].host}")
print(f"Production API port: {env_configs['api'].port}")
print(f"Production log level: {env_configs['logging'].level}")
# Test application setup
print("\nTesting application setup:")
app_configs = setup_application(Environment.DEVELOPMENT)
print("Application configured successfully!")
if __name__ == "__main__":
test_configuration_package()
3. Math Library Package
Create a comprehensive math package with modules for basic operations, statistics, and geometry.
💡 Show Answer
# math_lib/__init__.py
"""Math Library - A comprehensive mathematics package"""
__version__ = "1.0.0"
__author__ = "Python Developer"
# Import main modules
from . import basic_ops
from . import statistics as stats
from . import geometry
from . import constants
# Import commonly used functions for convenience
from .basic_ops import add, subtract, multiply, divide, power, factorial
from .statistics import mean, median, mode, std_dev
from .geometry import Circle, Rectangle, Triangle
from .constants import PI, E, GOLDEN_RATIO
# Package-level convenience functions
def calculate(expression: str) -> float:
"""Safe calculator for basic expressions"""
return basic_ops.safe_eval(expression)
def distance_2d(x1: float, y1: float, x2: float, y2: float) -> float:
"""Calculate 2D distance between two points"""
return geometry.distance((x1, y1), (x2, y2))
# Export main items
__all__ = [
# Modules
'basic_ops', 'stats', 'geometry', 'constants',
# Functions
'add', 'subtract', 'multiply', 'divide', 'power', 'factorial',
'mean', 'median', 'mode', 'std_dev',
'calculate', 'distance_2d',
# Classes
'Circle', 'Rectangle', 'Triangle',
# Constants
'PI', 'E', 'GOLDEN_RATIO'
]
# math_lib/constants.py
"""Mathematical constants"""
import math
# Basic constants
PI = 3.141592653589793
E = 2.718281828459045
GOLDEN_RATIO = 1.618033988749895
# Extended constants
TAU = 2 * PI # Full circle in radians
SQRT_2 = math.sqrt(2)
SQRT_3 = math.sqrt(3)
SQRT_5 = math.sqrt(5)
# Physical constants (for reference)
SPEED_OF_LIGHT = 299_792_458 # m/s
PLANCK_CONSTANT = 6.62607015e-34 # J⋅Hz⁻¹
AVOGADRO_NUMBER = 6.02214076e23 # mol⁻¹
# Mathematical limits
INFINITY = float('inf')
NEG_INFINITY = float('-inf')
NAN = float('nan')
def get_all_constants():
"""Get all mathematical constants as a dictionary"""
return {
'PI': PI,
'E': E,
'GOLDEN_RATIO': GOLDEN_RATIO,
'TAU': TAU,
'SQRT_2': SQRT_2,
'SQRT_3': SQRT_3,
'SQRT_5': SQRT_5,
}
# math_lib/basic_ops.py
"""Basic mathematical operations"""
import re
from typing import Union
Number = Union[int, float]
def add(a: Number, b: Number) -> Number:
"""Add two numbers"""
return a + b
def subtract(a: Number, b: Number) -> Number:
"""Subtract b from a"""
return a - b
def multiply(a: Number, b: Number) -> Number:
"""Multiply two numbers"""
return a * b
def divide(a: Number, b: Number) -> float:
"""Divide a by b"""
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
def power(base: Number, exponent: Number) -> Number:
"""Raise base to the power of exponent"""
return base ** exponent
def factorial(n: int) -> int:
"""Calculate factorial of n"""
if not isinstance(n, int) or n < 0:
raise ValueError("Factorial is only defined for non-negative integers")
if n <= 1:
return 1
result = 1
for i in range(2, n + 1):
result *= i
return result
def gcd(a: int, b: int) -> int:
"""Calculate Greatest Common Divisor"""
while b:
a, b = b, a % b
return abs(a)
def lcm(a: int, b: int) -> int:
"""Calculate Least Common Multiple"""
return abs(a * b) // gcd(a, b) if a and b else 0
def is_prime(n: int) -> bool:
"""Check if number is prime"""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
for i in range(3, int(n**0.5) + 1, 2):
if n % i == 0:
return False
return True
def fibonacci(n: int) -> int:
"""Generate nth Fibonacci number"""
if n < 0:
raise ValueError("Fibonacci is only defined for non-negative integers")
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
def safe_eval(expression: str) -> float:
"""Safely evaluate mathematical expressions"""
# Remove whitespace
expression = expression.replace(" ", "")
# Check for allowed characters only
allowed_chars = set("0123456789+-*/.()^")
if not all(c in allowed_chars for c in expression):
raise ValueError("Invalid characters in expression")
# Replace ^ with **
expression = expression.replace("^", "**")
# Simple validation
if not re.match(r'^[0-9+\-*/.()^]+$', expression.replace("**", "^")):
raise ValueError("Invalid expression format")
try:
# Use eval with restricted globals
return eval(expression, {"__builtins__": {}})
except Exception as e:
raise ValueError(f"Error evaluating expression: {e}")
class Calculator:
"""Advanced calculator class"""
def __init__(self):
self.history = []
self.memory = 0
def calculate(self, expression: str) -> float:
"""Calculate expression and store in history"""
try:
result = safe_eval(expression)
self.history.append(f"{expression} = {result}")
return result
except Exception as e:
error_msg = f"{expression} = ERROR: {e}"
self.history.append(error_msg)
raise
def memory_store(self, value: Number):
"""Store value in memory"""
self.memory = value
def memory_recall(self) -> Number:
"""Recall value from memory"""
return self.memory
def memory_clear(self):
"""Clear memory"""
self.memory = 0
def get_history(self, limit: int = 10) -> list:
"""Get calculation history"""
return self.history[-limit:]
def clear_history(self):
"""Clear calculation history"""
self.history.clear()
# math_lib/statistics.py
"""Statistical functions"""
from typing import List, Union
import math
Number = Union[int, float]
def mean(data: List[Number]) -> float:
"""Calculate arithmetic mean"""
if not data:
raise ValueError("Cannot calculate mean of empty list")
return sum(data) / len(data)
def median(data: List[Number]) -> float:
"""Calculate median"""
if not data:
raise ValueError("Cannot calculate median of empty list")
sorted_data = sorted(data)
n = len(sorted_data)
if n % 2 == 0:
return (sorted_data[n//2 - 1] + sorted_data[n//2]) / 2
else:
return sorted_data[n//2]
def mode(data: List[Number]) -> List[Number]:
"""Calculate mode(s)"""
if not data:
raise ValueError("Cannot calculate mode of empty list")
frequency = {}
for value in data:
frequency[value] = frequency.get(value, 0) + 1
max_freq = max(frequency.values())
modes = [value for value, freq in frequency.items() if freq == max_freq]
return modes
def variance(data: List[Number], sample: bool = True) -> float:
"""Calculate variance"""
if not data:
raise ValueError("Cannot calculate variance of empty list")
if len(data) == 1 and sample:
raise ValueError("Cannot calculate sample variance with single data point")
avg = mean(data)
squared_diffs = [(x - avg) ** 2 for x in data]
denominator = len(data) - 1 if sample else len(data)
return sum(squared_diffs) / denominator
def std_dev(data: List[Number], sample: bool = True) -> float:
"""Calculate standard deviation"""
return math.sqrt(variance(data, sample))
def range_stat(data: List[Number]) -> Number:
"""Calculate range (max - min)"""
if not data:
raise ValueError("Cannot calculate range of empty list")
return max(data) - min(data)
def quartiles(data: List[Number]) -> tuple:
"""Calculate Q1, Q2 (median), Q3"""
if not data:
raise ValueError("Cannot calculate quartiles of empty list")
sorted_data = sorted(data)
n = len(sorted_data)
q2 = median(sorted_data)
if n % 2 == 0:
lower_half = sorted_data[:n//2]
upper_half = sorted_data[n//2:]
else:
lower_half = sorted_data[:n//2]
upper_half = sorted_data[n//2 + 1:]
q1 = median(lower_half) if lower_half else sorted_data[0]
q3 = median(upper_half) if upper_half else sorted_data[-1]
return (q1, q2, q3)
def correlation(x_data: List[Number], y_data: List[Number]) -> float:
"""Calculate Pearson correlation coefficient"""
if len(x_data) != len(y_data):
raise ValueError("x_data and y_data must have same length")
if len(x_data) < 2:
raise ValueError("Need at least 2 data points")
n = len(x_data)
sum_x = sum(x_data)
sum_y = sum(y_data)
sum_xy = sum(x * y for x, y in zip(x_data, y_data))
sum_x2 = sum(x * x for x in x_data)
sum_y2 = sum(y * y for y in y_data)
numerator = n * sum_xy - sum_x * sum_y
denominator = math.sqrt((n * sum_x2 - sum_x**2) * (n * sum_y2 - sum_y**2))
if denominator == 0:
return 0 # No correlation if no variance
return numerator / denominator
class StatisticalSummary:
"""Statistical summary for a dataset"""
def __init__(self, data: List[Number]):
if not data:
raise ValueError("Dataset cannot be empty")
self.data = sorted(data)
self.count = len(data)
self.mean = mean(data)
self.median = median(data)
self.mode = mode(data)
self.std_dev = std_dev(data)
self.variance = variance(data)
self.min = min(data)
self.max = max(data)
self.range = range_stat(data)
self.q1, self.q2, self.q3 = quartiles(data)
def __str__(self):
return (f"Statistical Summary:\n"
f" Count: {self.count}\n"
f" Mean: {self.mean:.2f}\n"
f" Median: {self.median:.2f}\n"
f" Mode: {self.mode}\n"
f" Std Dev: {self.std_dev:.2f}\n"
f" Min: {self.min}\n"
f" Max: {self.max}\n"
f" Range: {self.range}\n"
f" Q1: {self.q1:.2f}\n"
f" Q3: {self.q3:.2f}")
# math_lib/geometry.py
"""Geometric calculations and shapes"""
import math
from typing import Tuple, Union
from .constants import PI
Point = Tuple[float, float]
Number = Union[int, float]
def distance(p1: Point, p2: Point) -> float:
"""Calculate distance between two points"""
return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)
def midpoint(p1: Point, p2: Point) -> Point:
"""Calculate midpoint between two points"""
return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)
class Shape:
"""Base class for geometric shapes"""
def area(self) -> float:
"""Calculate area"""
raise NotImplementedError("Subclasses must implement area()")
def perimeter(self) -> float:
"""Calculate perimeter"""
raise NotImplementedError("Subclasses must implement perimeter()")
class Circle(Shape):
"""Circle shape"""
def __init__(self, radius: Number):
if radius <= 0:
raise ValueError("Radius must be positive")
self.radius = radius
def area(self) -> float:
"""Calculate circle area"""
return PI * self.radius ** 2
def perimeter(self) -> float:
"""Calculate circle circumference"""
return 2 * PI * self.radius
def diameter(self) -> float:
"""Calculate diameter"""
return 2 * self.radius
def __str__(self):
return f"Circle(radius={self.radius})"
class Rectangle(Shape):
"""Rectangle shape"""
def __init__(self, width: Number, height: Number):
if width <= 0 or height <= 0:
raise ValueError("Width and height must be positive")
self.width = width
self.height = height
def area(self) -> float:
"""Calculate rectangle area"""
return self.width * self.height
def perimeter(self) -> float:
"""Calculate rectangle perimeter"""
return 2 * (self.width + self.height)
def diagonal(self) -> float:
"""Calculate diagonal length"""
return math.sqrt(self.width**2 + self.height**2)
def __str__(self):
return f"Rectangle(width={self.width}, height={self.height})"
class Triangle(Shape):
"""Triangle shape"""
def __init__(self, side_a: Number, side_b: Number, side_c: Number):
if side_a <= 0 or side_b <= 0 or side_c <= 0:
raise ValueError("All sides must be positive")
# Check triangle inequality
if (side_a + side_b <= side_c or
side_a + side_c <= side_b or
side_b + side_c <= side_a):
raise ValueError("Invalid triangle: sides don't satisfy triangle inequality")
self.side_a = side_a
self.side_b = side_b
self.side_c = side_c
def area(self) -> float:
"""Calculate triangle area using Heron's formula"""
s = self.perimeter() / 2 # semi-perimeter
return math.sqrt(s * (s - self.side_a) * (s - self.side_b) * (s - self.side_c))
def perimeter(self) -> float:
"""Calculate triangle perimeter"""
return self.side_a + self.side_b + self.side_c
def is_right_triangle(self) -> bool:
"""Check if triangle is a right triangle"""
sides = sorted([self.side_a, self.side_b, self.side_c])
return abs(sides[0]**2 + sides[1]**2 - sides[2]**2) < 1e-10
def is_equilateral(self) -> bool:
"""Check if triangle is equilateral"""
return (abs(self.side_a - self.side_b) < 1e-10 and
abs(self.side_b - self.side_c) < 1e-10)
def is_isosceles(self) -> bool:
"""Check if triangle is isosceles"""
return (abs(self.side_a - self.side_b) < 1e-10 or
abs(self.side_b - self.side_c) < 1e-10 or
abs(self.side_a - self.side_c) < 1e-10)
def __str__(self):
return f"Triangle(sides={self.side_a}, {self.side_b}, {self.side_c})"
def shapes_demo():
"""Demonstrate geometric shapes"""
print("Geometry Demo:")
# Circle
circle = Circle(5)
print(f"{circle}")
print(f" Area: {circle.area():.2f}")
print(f" Circumference: {circle.perimeter():.2f}")
# Rectangle
rectangle = Rectangle(4, 6)
print(f"\n{rectangle}")
print(f" Area: {rectangle.area():.2f}")
print(f" Perimeter: {rectangle.perimeter():.2f}")
print(f" Diagonal: {rectangle.diagonal():.2f}")
# Triangle
triangle = Triangle(3, 4, 5)
print(f"\n{triangle}")
print(f" Area: {triangle.area():.2f}")
print(f" Perimeter: {triangle.perimeter():.2f}")
print(f" Is right triangle: {triangle.is_right_triangle()}")
# test_math_lib.py - Using the math library package
from math_lib import (
add, multiply, factorial, mean, std_dev,
Circle, Rectangle, Triangle,
PI, E, StatisticalSummary
)
import math_lib
def test_math_library():
"""Test the complete math library package"""
print("Testing Math Library Package")
print("=" * 40)
# Test basic operations
print("Basic Operations:")
print(f" 5 + 3 = {add(5, 3)}")
print(f" 4 × 7 = {multiply(4, 7)}")
print(f" 5! = {factorial(5)}")
# Test statistics
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(f"\nStatistics for {data}:")
print(f" Mean: {mean(data):.2f}")
print(f" Std Dev: {std_dev(data):.2f}")
# Statistical summary
summary = StatisticalSummary(data)
print(f"\n{summary}")
# Test geometry
print(f"\nGeometry:")
circle = Circle(3)
rectangle = Rectangle(4, 5)
triangle = Triangle(3, 4, 5)
print(f" Circle (r=3): Area = {circle.area():.2f}")
print(f" Rectangle (4×5): Area = {rectangle.area():.2f}")
print(f" Triangle (3,4,5): Area = {triangle.area():.2f}")
# Test constants
print(f"\nConstants:")
print(f" π = {PI}")
print(f" e = {E}")
# Test package-level functions
print(f"\nPackage functions:")
print(f" calculate('2+3*4') = {math_lib.calculate('2+3*4')}")
print(f" distance_2d(0,0,3,4) = {math_lib.distance_2d(0, 0, 3, 4)}")
if __name__ == "__main__":
test_math_library()