that initial commit
This commit is contained in:
734
docs/standards/utility-function-patterns.md
Normal file
734
docs/standards/utility-function-patterns.md
Normal file
@@ -0,0 +1,734 @@
|
||||
# Utility Function Patterns
|
||||
|
||||
## Purpose
|
||||
|
||||
This document establishes coding patterns and conventions specifically for utility functions in StarPunk. These patterns ensure utilities are consistent, testable, and maintainable.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Utility functions should be:
|
||||
- **Pure**: No side effects where possible
|
||||
- **Focused**: One responsibility per function
|
||||
- **Predictable**: Same input always produces same output
|
||||
- **Testable**: Easy to test in isolation
|
||||
- **Documented**: Clear purpose and usage
|
||||
|
||||
## Function Design Patterns
|
||||
|
||||
### 1. Pure Functions (Preferred)
|
||||
|
||||
**Pattern**: Functions that don't modify state or have side effects.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def calculate_content_hash(content: str) -> str:
|
||||
"""Calculate SHA-256 hash of content."""
|
||||
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
||||
```
|
||||
|
||||
**Why**: Easy to test, no hidden dependencies, predictable behavior.
|
||||
|
||||
**When to Use**: Calculations, transformations, validations.
|
||||
|
||||
### 2. Functions with I/O Side Effects
|
||||
|
||||
**Pattern**: Functions that read/write files or interact with external systems.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def write_note_file(file_path: Path, content: str) -> None:
|
||||
"""Write note content to file atomically."""
|
||||
temp_path = file_path.with_suffix(file_path.suffix + '.tmp')
|
||||
try:
|
||||
temp_path.write_text(content, encoding='utf-8')
|
||||
temp_path.replace(file_path)
|
||||
except Exception:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
raise
|
||||
```
|
||||
|
||||
**Why**: Side effects are isolated, error handling is explicit, cleanup is guaranteed.
|
||||
|
||||
**When to Use**: File operations, database operations, network calls.
|
||||
|
||||
### 3. Validation Functions
|
||||
|
||||
**Pattern**: Functions that check validity and return boolean or raise exception.
|
||||
|
||||
**Good Example** (Boolean Return):
|
||||
```python
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate that slug meets requirements."""
|
||||
if not slug:
|
||||
return False
|
||||
if len(slug) > MAX_SLUG_LENGTH:
|
||||
return False
|
||||
return bool(SLUG_PATTERN.match(slug))
|
||||
```
|
||||
|
||||
**Good Example** (Exception Raising):
|
||||
```python
|
||||
def require_valid_slug(slug: str) -> None:
|
||||
"""Require slug to be valid, raise ValueError if not."""
|
||||
if not validate_slug(slug):
|
||||
raise ValueError(
|
||||
f"Invalid slug '{slug}': must be 1-100 characters, "
|
||||
f"lowercase alphanumeric with hyphens only"
|
||||
)
|
||||
```
|
||||
|
||||
**When to Use**: Input validation, precondition checking, security checks.
|
||||
|
||||
### 4. Generator Functions
|
||||
|
||||
**Pattern**: Functions that yield values instead of returning lists.
|
||||
|
||||
**Good Example**:
|
||||
```python
|
||||
def iter_note_files(notes_dir: Path) -> Iterator[Path]:
|
||||
"""Iterate over all note files in directory."""
|
||||
for year_dir in sorted(notes_dir.iterdir()):
|
||||
if not year_dir.is_dir():
|
||||
continue
|
||||
for month_dir in sorted(year_dir.iterdir()):
|
||||
if not month_dir.is_dir():
|
||||
continue
|
||||
for note_file in sorted(month_dir.glob("*.md")):
|
||||
yield note_file
|
||||
```
|
||||
|
||||
**Why**: Memory efficient, lazy evaluation, can be interrupted.
|
||||
|
||||
**When to Use**: Processing many items, large datasets, streaming operations.
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### 1. Specific Exceptions
|
||||
|
||||
**Pattern**: Raise specific exception types with descriptive messages.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty or whitespace-only")
|
||||
|
||||
# ... rest of function
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
if not content:
|
||||
raise Exception("Bad input") # Too generic
|
||||
```
|
||||
|
||||
### 2. Error Message Format
|
||||
|
||||
**Pattern**: Include context, expected behavior, and actual problem.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
raise ValueError(
|
||||
f"Invalid slug '{slug}': must contain only lowercase letters, "
|
||||
f"numbers, and hyphens (pattern: {SLUG_PATTERN.pattern})"
|
||||
)
|
||||
|
||||
raise FileNotFoundError(
|
||||
f"Note file not found: {file_path}. "
|
||||
f"Database may be out of sync with filesystem."
|
||||
)
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
raise ValueError("Invalid slug")
|
||||
raise FileNotFoundError("File missing")
|
||||
```
|
||||
|
||||
### 3. Exception Chaining
|
||||
|
||||
**Pattern**: Preserve original exception context when re-raising.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def read_note_file(file_path: Path) -> str:
|
||||
"""Read note content from file."""
|
||||
try:
|
||||
return file_path.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(
|
||||
f"Failed to read {file_path}: invalid UTF-8 encoding"
|
||||
) from e
|
||||
```
|
||||
|
||||
**Why**: Preserves stack trace, shows root cause.
|
||||
|
||||
### 4. Cleanup on Error
|
||||
|
||||
**Pattern**: Use try/finally or context managers to ensure cleanup.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def write_note_file(file_path: Path, content: str) -> None:
|
||||
"""Write note content to file atomically."""
|
||||
temp_path = file_path.with_suffix('.tmp')
|
||||
try:
|
||||
temp_path.write_text(content, encoding='utf-8')
|
||||
temp_path.replace(file_path)
|
||||
except Exception:
|
||||
temp_path.unlink(missing_ok=True) # Cleanup on error
|
||||
raise
|
||||
```
|
||||
|
||||
## Type Hint Patterns
|
||||
|
||||
### 1. Basic Types
|
||||
|
||||
**Pattern**: Use built-in types where possible.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""Generate URL-safe slug from content."""
|
||||
pass
|
||||
|
||||
def validate_note_path(file_path: Path, data_dir: Path) -> bool:
|
||||
"""Validate file path is within data directory."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Collection Types
|
||||
|
||||
**Pattern**: Specify element types for collections.
|
||||
|
||||
```python
|
||||
from typing import List, Dict, Set, Optional
|
||||
|
||||
def make_slug_unique(base_slug: str, existing_slugs: Set[str]) -> str:
|
||||
"""Make slug unique by adding suffix if needed."""
|
||||
pass
|
||||
|
||||
def get_note_paths(notes_dir: Path) -> List[Path]:
|
||||
"""Get all note file paths."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 3. Optional Types
|
||||
|
||||
**Pattern**: Use Optional[T] for nullable parameters and returns.
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
def find_note(slug: str) -> Optional[Path]:
|
||||
"""Find note file by slug, returns None if not found."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 4. Union Types (Use Sparingly)
|
||||
|
||||
**Pattern**: Use Union when a parameter truly accepts multiple types.
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
|
||||
def ensure_path(path: Union[str, Path]) -> Path:
|
||||
"""Convert string or Path to Path object."""
|
||||
return Path(path) if isinstance(path, str) else path
|
||||
```
|
||||
|
||||
**Note**: Prefer single types. Only use Union when necessary.
|
||||
|
||||
## Documentation Patterns
|
||||
|
||||
### 1. Function Docstrings
|
||||
|
||||
**Pattern**: Google-style docstrings with sections.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""
|
||||
Generate URL-safe slug from note content
|
||||
|
||||
Creates a slug by extracting the first few words from the content and
|
||||
normalizing them to lowercase with hyphens. If content is insufficient,
|
||||
falls back to timestamp-based slug.
|
||||
|
||||
Args:
|
||||
content: The note content (markdown text)
|
||||
created_at: Optional timestamp for fallback slug (defaults to now)
|
||||
|
||||
Returns:
|
||||
URL-safe slug string (lowercase, alphanumeric + hyphens only)
|
||||
|
||||
Raises:
|
||||
ValueError: If content is empty or contains only whitespace
|
||||
|
||||
Examples:
|
||||
>>> generate_slug("Hello World! This is my first note.")
|
||||
'hello-world-this-is-my'
|
||||
|
||||
>>> generate_slug("Testing... with special chars!@#")
|
||||
'testing-with-special-chars'
|
||||
|
||||
Notes:
|
||||
- This function does NOT check for uniqueness
|
||||
- Caller must verify slug doesn't exist in database
|
||||
"""
|
||||
```
|
||||
|
||||
**Required Sections**:
|
||||
- Summary (first line)
|
||||
- Description (paragraph after summary)
|
||||
- Args (if any parameters)
|
||||
- Returns (if returns value)
|
||||
- Raises (if raises exceptions)
|
||||
|
||||
**Optional Sections**:
|
||||
- Examples (highly recommended)
|
||||
- Notes (for important caveats)
|
||||
- References (for external specs)
|
||||
|
||||
### 2. Inline Comments
|
||||
|
||||
**Pattern**: Explain why, not what.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
# Use atomic rename to prevent file corruption if interrupted
|
||||
temp_path.replace(file_path)
|
||||
|
||||
# Random suffix prevents enumeration attacks
|
||||
suffix = secrets.token_urlsafe(4)[:4]
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
# Rename temp file to final path
|
||||
temp_path.replace(file_path)
|
||||
|
||||
# Generate suffix
|
||||
suffix = secrets.token_urlsafe(4)[:4]
|
||||
```
|
||||
|
||||
### 3. Module Docstrings
|
||||
|
||||
**Pattern**: Describe module purpose and contents.
|
||||
|
||||
```python
|
||||
"""
|
||||
Core utility functions for StarPunk
|
||||
|
||||
This module provides essential utilities for slug generation, file operations,
|
||||
hashing, and date/time handling. These utilities are used throughout the
|
||||
application and have no external dependencies beyond standard library and
|
||||
Flask configuration.
|
||||
|
||||
Functions:
|
||||
generate_slug: Create URL-safe slug from content
|
||||
calculate_content_hash: Calculate SHA-256 hash
|
||||
write_note_file: Atomically write note to file
|
||||
format_rfc822: Format datetime for RSS feeds
|
||||
|
||||
Constants:
|
||||
MAX_SLUG_LENGTH: Maximum slug length (100)
|
||||
SLUG_PATTERN: Regex for valid slugs
|
||||
"""
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### 1. Test Function Naming
|
||||
|
||||
**Pattern**: `test_{function_name}_{scenario}`
|
||||
|
||||
```python
|
||||
def test_generate_slug_from_content():
|
||||
"""Test basic slug generation from content."""
|
||||
slug = generate_slug("Hello World This Is My Note")
|
||||
assert slug == "hello-world-this-is-my"
|
||||
|
||||
def test_generate_slug_empty_content():
|
||||
"""Test slug generation raises error on empty content."""
|
||||
with pytest.raises(ValueError):
|
||||
generate_slug("")
|
||||
|
||||
def test_generate_slug_special_characters():
|
||||
"""Test slug generation removes special characters."""
|
||||
slug = generate_slug("Testing... with!@# special chars")
|
||||
assert slug == "testing-with-special-chars"
|
||||
```
|
||||
|
||||
### 2. Test Organization
|
||||
|
||||
**Pattern**: Group related tests in classes.
|
||||
|
||||
```python
|
||||
class TestSlugGeneration:
|
||||
"""Test slug generation functions"""
|
||||
|
||||
def test_generate_slug_from_content(self):
|
||||
"""Test basic slug generation."""
|
||||
pass
|
||||
|
||||
def test_generate_slug_empty_content(self):
|
||||
"""Test error on empty content."""
|
||||
pass
|
||||
|
||||
|
||||
class TestContentHashing:
|
||||
"""Test content hashing functions"""
|
||||
|
||||
def test_calculate_hash_consistency(self):
|
||||
"""Test hash is consistent."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 3. Fixtures for Common Setup
|
||||
|
||||
**Pattern**: Use pytest fixtures for reusable test data.
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@pytest.fixture
|
||||
def temp_note_file(tmp_path):
|
||||
"""Create temporary note file for testing."""
|
||||
file_path = tmp_path / "test.md"
|
||||
file_path.write_text("# Test Note")
|
||||
return file_path
|
||||
|
||||
def test_read_note_file(temp_note_file):
|
||||
"""Test reading note file."""
|
||||
content = read_note_file(temp_note_file)
|
||||
assert content == "# Test Note"
|
||||
```
|
||||
|
||||
### 4. Parameterized Tests
|
||||
|
||||
**Pattern**: Test multiple cases with one test function.
|
||||
|
||||
```python
|
||||
@pytest.mark.parametrize("content,expected", [
|
||||
("Hello World", "hello-world"),
|
||||
("Testing 123", "testing-123"),
|
||||
("Special!@# Chars", "special-chars"),
|
||||
])
|
||||
def test_generate_slug_variations(content, expected):
|
||||
"""Test slug generation with various inputs."""
|
||||
slug = generate_slug(content)
|
||||
assert slug == expected
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### 1. Path Validation
|
||||
|
||||
**Pattern**: Always validate paths before file operations.
|
||||
|
||||
```python
|
||||
def safe_file_operation(file_path: Path, data_dir: Path) -> None:
|
||||
"""Perform file operation with path validation."""
|
||||
# ALWAYS validate first
|
||||
if not validate_note_path(file_path, data_dir):
|
||||
raise ValueError(f"Invalid file path: {file_path}")
|
||||
|
||||
# Now safe to operate
|
||||
file_path.write_text("content")
|
||||
```
|
||||
|
||||
### 2. Use secrets for Random
|
||||
|
||||
**Pattern**: Use `secrets` module, not `random` for security-sensitive operations.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
import secrets
|
||||
|
||||
def generate_random_suffix(length: int = 4) -> str:
|
||||
"""Generate cryptographically secure random suffix."""
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(secrets.choice(chars) for _ in range(length))
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
import random
|
||||
|
||||
def generate_random_suffix(length: int = 4) -> str:
|
||||
"""Generate random suffix."""
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(random.choice(chars) for _ in range(length)) # NOT secure
|
||||
```
|
||||
|
||||
### 3. Input Sanitization
|
||||
|
||||
**Pattern**: Validate and sanitize all external input.
|
||||
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
# Validate input
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty")
|
||||
|
||||
# Sanitize by removing dangerous characters
|
||||
normalized = normalize_slug_text(content)
|
||||
|
||||
# Additional validation
|
||||
if not validate_slug(normalized):
|
||||
# Fallback to safe default
|
||||
normalized = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return normalized
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### 1. Lazy Evaluation
|
||||
|
||||
**Pattern**: Don't compute what you don't need.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str, created_at: Optional[datetime] = None) -> str:
|
||||
"""Generate slug from content."""
|
||||
slug = normalize_slug_text(content)
|
||||
|
||||
# Only generate timestamp if needed
|
||||
if not slug:
|
||||
created_at = created_at or datetime.utcnow()
|
||||
slug = created_at.strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return slug
|
||||
```
|
||||
|
||||
### 2. Compile Regex Once
|
||||
|
||||
**Pattern**: Define regex patterns as module constants.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
# Module level - compiled once
|
||||
SLUG_PATTERN = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')
|
||||
SAFE_SLUG_PATTERN = re.compile(r'[^a-z0-9-]')
|
||||
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate slug."""
|
||||
return bool(SLUG_PATTERN.match(slug))
|
||||
|
||||
def normalize_slug_text(text: str) -> str:
|
||||
"""Normalize text for slug."""
|
||||
return SAFE_SLUG_PATTERN.sub('', text)
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def validate_slug(slug: str) -> bool:
|
||||
"""Validate slug."""
|
||||
# Compiles regex every call - inefficient
|
||||
return bool(re.match(r'^[a-z0-9]+(?:-[a-z0-9]+)*$', slug))
|
||||
```
|
||||
|
||||
### 3. Avoid Premature Optimization
|
||||
|
||||
**Pattern**: Write clear code first, optimize if needed.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def extract_first_words(text: str, max_words: int = 5) -> str:
|
||||
"""Extract first N words from text."""
|
||||
words = text.split()
|
||||
return ' '.join(words[:max_words])
|
||||
```
|
||||
|
||||
**Don't Do This Unless Profiling Shows It's Necessary**:
|
||||
```python
|
||||
def extract_first_words(text: str, max_words: int = 5) -> str:
|
||||
"""Extract first N words from text."""
|
||||
# Premature optimization - more complex, minimal gain
|
||||
words = []
|
||||
count = 0
|
||||
for word in text.split():
|
||||
words.append(word)
|
||||
count += 1
|
||||
if count >= max_words:
|
||||
break
|
||||
return ' '.join(words)
|
||||
```
|
||||
|
||||
## Constants Pattern
|
||||
|
||||
### 1. Module-Level Constants
|
||||
|
||||
**Pattern**: Define configuration as constants at module level.
|
||||
|
||||
```python
|
||||
# Slug configuration
|
||||
MAX_SLUG_LENGTH = 100
|
||||
MIN_SLUG_LENGTH = 1
|
||||
SLUG_WORDS_COUNT = 5
|
||||
RANDOM_SUFFIX_LENGTH = 4
|
||||
|
||||
# File operations
|
||||
TEMP_FILE_SUFFIX = '.tmp'
|
||||
TRASH_DIR_NAME = '.trash'
|
||||
|
||||
# Hashing
|
||||
CONTENT_HASH_ALGORITHM = 'sha256'
|
||||
```
|
||||
|
||||
**Why**: Easy to modify, clear intent, compile-time resolution.
|
||||
|
||||
### 2. Naming Convention
|
||||
|
||||
**Pattern**: ALL_CAPS_WITH_UNDERSCORES
|
||||
|
||||
```python
|
||||
MAX_NOTE_LENGTH = 10000 # Good
|
||||
DEFAULT_ENCODING = 'utf-8' # Good
|
||||
maxNoteLength = 10000 # Bad
|
||||
max_note_length = 10000 # Bad (looks like variable)
|
||||
```
|
||||
|
||||
### 3. Magic Numbers
|
||||
|
||||
**Pattern**: Replace magic numbers with named constants.
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
SLUG_WORDS_COUNT = 5
|
||||
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
words = content.split()[:SLUG_WORDS_COUNT]
|
||||
return normalize_slug_text(' '.join(words))
|
||||
```
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug from content."""
|
||||
words = content.split()[:5] # What is 5? Why 5?
|
||||
return normalize_slug_text(' '.join(words))
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### 1. God Functions
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def process_note(content, do_hash=True, do_slug=True, do_path=True, ...):
|
||||
"""Do everything with a note."""
|
||||
# 500 lines of code doing too many things
|
||||
pass
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
pass
|
||||
|
||||
def calculate_hash(content: str) -> str:
|
||||
"""Calculate hash."""
|
||||
pass
|
||||
|
||||
def generate_path(slug: str, timestamp: datetime) -> Path:
|
||||
"""Generate path."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Mutable Default Arguments
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def create_note(content: str, tags: list = []) -> Note:
|
||||
tags.append('note') # Modifies shared default list!
|
||||
return Note(content, tags)
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def create_note(content: str, tags: Optional[List[str]] = None) -> Note:
|
||||
if tags is None:
|
||||
tags = []
|
||||
tags.append('note')
|
||||
return Note(content, tags)
|
||||
```
|
||||
|
||||
### 3. Returning Multiple Types
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def find_note(slug: str) -> Union[Note, bool, None]:
|
||||
"""Find note by slug."""
|
||||
if slug_invalid:
|
||||
return False
|
||||
note = db.query(slug)
|
||||
return note if note else None
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def find_note(slug: str) -> Optional[Note]:
|
||||
"""Find note by slug, returns None if not found."""
|
||||
if not validate_slug(slug):
|
||||
raise ValueError(f"Invalid slug: {slug}")
|
||||
return db.query(slug) # Returns Note or None
|
||||
```
|
||||
|
||||
### 4. Silent Failures
|
||||
|
||||
**Bad**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
try:
|
||||
slug = normalize_slug_text(content)
|
||||
return slug if slug else "untitled" # Silent fallback
|
||||
except Exception:
|
||||
return "untitled" # Swallowing errors
|
||||
```
|
||||
|
||||
**Good**:
|
||||
```python
|
||||
def generate_slug(content: str) -> str:
|
||||
"""Generate slug."""
|
||||
if not content or not content.strip():
|
||||
raise ValueError("Content cannot be empty")
|
||||
|
||||
slug = normalize_slug_text(content)
|
||||
if not slug:
|
||||
# Explicit fallback with timestamp
|
||||
slug = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
return slug
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
**Key Principles**:
|
||||
1. Pure functions are preferred
|
||||
2. Specific exceptions with clear messages
|
||||
3. Type hints on all functions
|
||||
4. Comprehensive docstrings
|
||||
5. Security-first validation
|
||||
6. Test everything thoroughly
|
||||
7. Constants for configuration
|
||||
8. Clear over clever
|
||||
|
||||
**Remember**: Utility functions are the foundation. Make them rock-solid.
|
||||
|
||||
## References
|
||||
|
||||
- [Python Coding Standards](/home/phil/Projects/starpunk/docs/standards/python-coding-standards.md)
|
||||
- [PEP 8 - Style Guide](https://peps.python.org/pep-0008/)
|
||||
- [PEP 257 - Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
- [PEP 484 - Type Hints](https://peps.python.org/pep-0484/)
|
||||
- [Python secrets Documentation](https://docs.python.org/3/library/secrets.html)
|
||||
- [Pytest Best Practices](https://docs.pytest.org/en/stable/goodpractices.html)
|
||||
Reference in New Issue
Block a user