npv2k1 commited on
Commit
696363c
·
1 Parent(s): 3ec4ff0

feat: enhance development setup with Black and Ruff configuration, update README, and remove unused loader module

Browse files
.vscode/settings.json CHANGED
@@ -1,5 +1,17 @@
1
  {
2
  "python.testing.pytestArgs": ["tests"],
3
  "python.testing.unittestEnabled": false,
4
- "python.testing.pytestEnabled": true
 
 
 
 
 
 
 
 
 
 
 
 
5
  }
 
1
  {
2
  "python.testing.pytestArgs": ["tests"],
3
  "python.testing.unittestEnabled": false,
4
+ "python.testing.pytestEnabled": true,
5
+ "[python]": {
6
+ "editor.formatOnSave": true,
7
+ "editor.defaultFormatter": "ms-python.black-formatter",
8
+ "editor.codeActionsOnSave": {
9
+ "source.organizeImports": "explicit",
10
+ "source.fixAll": "explicit"
11
+ }
12
+ },
13
+ "editor.formatOnSave": true,
14
+ "python.analysis.typeCheckingMode": "basic",
15
+ "ruff.organizeImports": true,
16
+ "ruff.fixAll": true
17
  }
docs/README.md CHANGED
@@ -17,11 +17,32 @@ This template provides a foundation for Python projects with:
17
 
18
  - Modern Python project structure
19
  - Development tooling configuration
 
 
 
20
  - Testing framework setup
21
  - Docker support
22
  - Documentation templates
23
  - CI/CD examples
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  ## Quick Links
26
 
27
  - [Installation Guide](getting-started.md#installation)
 
17
 
18
  - Modern Python project structure
19
  - Development tooling configuration
20
+ - Black for code formatting
21
+ - Ruff for linting and import sorting
22
+ - Type checking support
23
  - Testing framework setup
24
  - Docker support
25
  - Documentation templates
26
  - CI/CD examples
27
 
28
+ ## Development Setup
29
+
30
+ ### Code Quality Tools
31
+
32
+ This project uses modern Python code quality tools:
33
+
34
+ - **Black**: Code formatter that enforces a consistent style
35
+ - **Ruff**: Fast Python linter and import sorter
36
+ - Enforces PEP 8 style guide
37
+ - Sorts imports automatically
38
+ - Checks for common errors and anti-patterns
39
+ - Type checking enforcement
40
+
41
+ VSCode is configured to automatically:
42
+ - Format code on save using Black
43
+ - Run Ruff for linting and import sorting
44
+ - Provide type checking feedback
45
+
46
  ## Quick Links
47
 
48
  - [Installation Guide](getting-started.md#installation)
main.py CHANGED
@@ -6,10 +6,6 @@ from pathlib import Path
6
  from typing import Dict
7
 
8
 
9
-
10
-
11
-
12
-
13
  def main() -> None:
14
  """Main application entry point."""
15
  try:
@@ -22,4 +18,4 @@ def main() -> None:
22
 
23
 
24
  if __name__ == "__main__":
25
- main()
 
6
  from typing import Dict
7
 
8
 
 
 
 
 
9
  def main() -> None:
10
  """Main application entry point."""
11
  try:
 
18
 
19
 
20
  if __name__ == "__main__":
21
+ main()
pyproject.toml CHANGED
@@ -10,3 +10,30 @@ dependencies = [
10
  "numpy>=2.2.3",
11
  "pytest>=8.3.4",
12
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  "numpy>=2.2.3",
11
  "pytest>=8.3.4",
12
  ]
13
+
14
+ [tool.black]
15
+ line-length = 88
16
+ target-version = ["py311"]
17
+ include = '\.pyi?$'
18
+
19
+ [tool.ruff]
20
+ line-length = 88
21
+ target-version = "py311"
22
+ select = [
23
+ "E", # pycodestyle errors
24
+ "W", # pycodestyle warnings
25
+ "F", # pyflakes
26
+ "I", # isort
27
+ "C", # flake8-comprehensions
28
+ "B", # flake8-bugbear
29
+ ]
30
+ ignore = []
31
+
32
+ [tool.ruff.per-file-ignores]
33
+ "__init__.py" = ["F401"]
34
+
35
+ [tool.ruff.isort]
36
+ known-first-party = ["src"]
37
+
38
+ [tool.ruff.mccabe]
39
+ max-complexity = 10
src/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
  """
2
  Main application package.
3
  This is the root package of the application.
4
- """
 
1
  """
2
  Main application package.
3
  This is the root package of the application.
4
+ """
src/common/config.py CHANGED
@@ -6,18 +6,20 @@ from dataclasses import dataclass
6
  from pathlib import Path
7
  from typing import Any, Optional
8
 
 
9
  @dataclass
10
  class AppConfig:
11
  """Application configuration."""
 
12
  app_name: str
13
  environment: str
14
  debug: bool
15
  log_level: str
16
-
17
  @classmethod
18
  def from_env(cls) -> "AppConfig":
19
  """Create configuration from environment variables.
20
-
21
  Returns:
22
  AppConfig: Application configuration instance
23
  """
@@ -28,35 +30,39 @@ class AppConfig:
28
  log_level=os.getenv("LOG_LEVEL", "INFO"),
29
  )
30
 
 
31
  def load_json_config(path: Path) -> dict[str, Any]:
32
  """Load configuration from JSON file.
33
-
34
  Args:
35
  path: Path to JSON configuration file
36
-
37
  Returns:
38
  dict[str, Any]: Configuration dictionary
39
-
40
  Raises:
41
  FileNotFoundError: If configuration file doesn't exist
42
  json.JSONDecodeError: If configuration file is invalid JSON
43
  """
44
  if not path.exists():
45
  raise FileNotFoundError(f"Configuration file not found: {path}")
46
-
47
  with path.open() as f:
48
  return json.load(f)
49
 
 
50
  class ConfigurationError(Exception):
51
  """Base class for configuration errors."""
 
52
  pass
53
 
 
54
  def get_config_path(config_name: str) -> Path:
55
  """Get configuration file path.
56
-
57
  Args:
58
  config_name: Name of the configuration file
59
-
60
  Returns:
61
  Path: Path to configuration file
62
  """
@@ -66,10 +72,10 @@ def get_config_path(config_name: str) -> Path:
66
  Path("~/.config/template-python"), # User config directory
67
  Path("/etc/template-python"), # System config directory
68
  ]
69
-
70
  for location in locations:
71
  path = location.expanduser() / f"{config_name}.json"
72
  if path.exists():
73
  return path
74
-
75
- return locations[0].expanduser() / f"{config_name}.json"
 
6
  from pathlib import Path
7
  from typing import Any, Optional
8
 
9
+
10
  @dataclass
11
  class AppConfig:
12
  """Application configuration."""
13
+
14
  app_name: str
15
  environment: str
16
  debug: bool
17
  log_level: str
18
+
19
  @classmethod
20
  def from_env(cls) -> "AppConfig":
21
  """Create configuration from environment variables.
22
+
23
  Returns:
24
  AppConfig: Application configuration instance
25
  """
 
30
  log_level=os.getenv("LOG_LEVEL", "INFO"),
31
  )
32
 
33
+
34
  def load_json_config(path: Path) -> dict[str, Any]:
35
  """Load configuration from JSON file.
36
+
37
  Args:
38
  path: Path to JSON configuration file
39
+
40
  Returns:
41
  dict[str, Any]: Configuration dictionary
42
+
43
  Raises:
44
  FileNotFoundError: If configuration file doesn't exist
45
  json.JSONDecodeError: If configuration file is invalid JSON
46
  """
47
  if not path.exists():
48
  raise FileNotFoundError(f"Configuration file not found: {path}")
49
+
50
  with path.open() as f:
51
  return json.load(f)
52
 
53
+
54
  class ConfigurationError(Exception):
55
  """Base class for configuration errors."""
56
+
57
  pass
58
 
59
+
60
  def get_config_path(config_name: str) -> Path:
61
  """Get configuration file path.
62
+
63
  Args:
64
  config_name: Name of the configuration file
65
+
66
  Returns:
67
  Path: Path to configuration file
68
  """
 
72
  Path("~/.config/template-python"), # User config directory
73
  Path("/etc/template-python"), # System config directory
74
  ]
75
+
76
  for location in locations:
77
  path = location.expanduser() / f"{config_name}.json"
78
  if path.exists():
79
  return path
80
+
81
+ return locations[0].expanduser() / f"{config_name}.json"
src/shared/utils/validation.py CHANGED
@@ -5,43 +5,50 @@ from typing import Any, Optional, Pattern
5
 
6
  # Common validation patterns
7
  EMAIL_PATTERN: Pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
8
- UUID_PATTERN: Pattern = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
 
 
 
9
 
10
  def validate_email(email: str) -> bool:
11
  """Validate email format.
12
-
13
  Args:
14
  email: Email address to validate
15
-
16
  Returns:
17
  bool: True if email format is valid, False otherwise
18
  """
19
  return bool(EMAIL_PATTERN.match(email))
20
 
 
21
  def validate_uuid(uuid: str) -> bool:
22
  """Validate UUID format.
23
-
24
  Args:
25
  uuid: UUID string to validate
26
-
27
  Returns:
28
  bool: True if UUID format is valid, False otherwise
29
  """
30
  return bool(UUID_PATTERN.match(uuid.lower()))
31
 
32
- def validate_required_fields(data: dict[str, Any], required_fields: list[str]) -> tuple[bool, Optional[str]]:
 
 
 
33
  """Validate required fields in a dictionary.
34
-
35
  Args:
36
  data: Dictionary containing data to validate
37
  required_fields: List of required field names
38
-
39
  Returns:
40
  tuple[bool, Optional[str]]: (is_valid, error_message)
41
  """
42
  missing_fields = [field for field in required_fields if field not in data]
43
-
44
  if missing_fields:
45
  return False, f"Missing required fields: {', '.join(missing_fields)}"
46
-
47
- return True, None
 
5
 
6
  # Common validation patterns
7
  EMAIL_PATTERN: Pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
8
+ UUID_PATTERN: Pattern = re.compile(
9
+ r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
10
+ )
11
+
12
 
13
  def validate_email(email: str) -> bool:
14
  """Validate email format.
15
+
16
  Args:
17
  email: Email address to validate
18
+
19
  Returns:
20
  bool: True if email format is valid, False otherwise
21
  """
22
  return bool(EMAIL_PATTERN.match(email))
23
 
24
+
25
  def validate_uuid(uuid: str) -> bool:
26
  """Validate UUID format.
27
+
28
  Args:
29
  uuid: UUID string to validate
30
+
31
  Returns:
32
  bool: True if UUID format is valid, False otherwise
33
  """
34
  return bool(UUID_PATTERN.match(uuid.lower()))
35
 
36
+
37
+ def validate_required_fields(
38
+ data: dict[str, Any], required_fields: list[str]
39
+ ) -> tuple[bool, Optional[str]]:
40
  """Validate required fields in a dictionary.
41
+
42
  Args:
43
  data: Dictionary containing data to validate
44
  required_fields: List of required field names
45
+
46
  Returns:
47
  tuple[bool, Optional[str]]: (is_valid, error_message)
48
  """
49
  missing_fields = [field for field in required_fields if field not in data]
50
+
51
  if missing_fields:
52
  return False, f"Missing required fields: {', '.join(missing_fields)}"
53
+
54
+ return True, None
src/utils/loader.py DELETED
@@ -1,65 +0,0 @@
1
- """Module loader utilities."""
2
-
3
- import logging
4
- import uvicorn
5
- from typing import Any, Optional
6
-
7
- from src.modules.api.app import app as api_app
8
- from src.modules.api.core.config import settings
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- class ModuleLoader:
13
- """Utility class for loading and running modules."""
14
-
15
- @staticmethod
16
- def load_api(
17
- host: str = "0.0.0.0",
18
- port: int = 8000,
19
- reload: bool = False,
20
- **kwargs: Any
21
- ) -> None:
22
- """Load and run the API module.
23
-
24
- Args:
25
- host: Host to bind the server to
26
- port: Port to bind the server to
27
- reload: Enable auto-reload for development
28
- **kwargs: Additional uvicorn configuration
29
- """
30
- logger.info(f"Starting API server on {host}:{port}")
31
- logger.debug(f"API Settings: {settings.dict()}")
32
-
33
- config = uvicorn.Config(
34
- api_app,
35
- host=host,
36
- port=port,
37
- reload=reload,
38
- log_level="debug" if settings.debug else "info",
39
- **kwargs
40
- )
41
-
42
- server = uvicorn.Server(config)
43
- server.run()
44
-
45
- @classmethod
46
- def load_module(
47
- cls,
48
- module_name: str,
49
- **kwargs: Any
50
- ) -> Optional[Any]:
51
- """Generic module loader.
52
-
53
- Args:
54
- module_name: Name of the module to load
55
- **kwargs: Module-specific configuration
56
-
57
- Returns:
58
- Optional[Any]: Module instance if applicable
59
- """
60
- if module_name == "api":
61
- cls.load_api(**kwargs)
62
- return api_app
63
- else:
64
- logger.error(f"Unknown module: {module_name}")
65
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/math.py CHANGED
@@ -4,51 +4,53 @@ from typing import List, Union
4
 
5
  Number = Union[int, float]
6
 
 
7
  def calculate_mean(numbers: List[Number]) -> float:
8
  """Calculate the arithmetic mean of a list of numbers.
9
-
10
  Args:
11
  numbers: A list of integers or floating point numbers
12
-
13
  Returns:
14
  float: The arithmetic mean of the input numbers
15
-
16
  Raises:
17
  ValueError: If the input list is empty
18
  TypeError: If any element is not a number
19
  """
20
  if not numbers:
21
  raise ValueError("Cannot calculate mean of empty list")
22
-
23
  if not all(isinstance(x, (int, float)) for x in numbers):
24
  raise TypeError("All elements must be numbers")
25
-
26
  return sum(numbers) / len(numbers)
27
 
 
28
  def calculate_median(numbers: List[Number]) -> float:
29
  """Calculate the median value from a list of numbers.
30
-
31
  Args:
32
  numbers: A list of integers or floating point numbers
33
-
34
  Returns:
35
  float: The median value of the input numbers
36
-
37
  Raises:
38
  ValueError: If the input list is empty
39
  TypeError: If any element is not a number
40
  """
41
  if not numbers:
42
  raise ValueError("Cannot calculate median of empty list")
43
-
44
  if not all(isinstance(x, (int, float)) for x in numbers):
45
  raise TypeError("All elements must be numbers")
46
-
47
  sorted_numbers = sorted(numbers)
48
  length = len(sorted_numbers)
49
-
50
  if length % 2 == 0:
51
  mid = length // 2
52
  return (sorted_numbers[mid - 1] + sorted_numbers[mid]) / 2
53
  else:
54
- return sorted_numbers[length // 2]
 
4
 
5
  Number = Union[int, float]
6
 
7
+
8
  def calculate_mean(numbers: List[Number]) -> float:
9
  """Calculate the arithmetic mean of a list of numbers.
10
+
11
  Args:
12
  numbers: A list of integers or floating point numbers
13
+
14
  Returns:
15
  float: The arithmetic mean of the input numbers
16
+
17
  Raises:
18
  ValueError: If the input list is empty
19
  TypeError: If any element is not a number
20
  """
21
  if not numbers:
22
  raise ValueError("Cannot calculate mean of empty list")
23
+
24
  if not all(isinstance(x, (int, float)) for x in numbers):
25
  raise TypeError("All elements must be numbers")
26
+
27
  return sum(numbers) / len(numbers)
28
 
29
+
30
  def calculate_median(numbers: List[Number]) -> float:
31
  """Calculate the median value from a list of numbers.
32
+
33
  Args:
34
  numbers: A list of integers or floating point numbers
35
+
36
  Returns:
37
  float: The median value of the input numbers
38
+
39
  Raises:
40
  ValueError: If the input list is empty
41
  TypeError: If any element is not a number
42
  """
43
  if not numbers:
44
  raise ValueError("Cannot calculate median of empty list")
45
+
46
  if not all(isinstance(x, (int, float)) for x in numbers):
47
  raise TypeError("All elements must be numbers")
48
+
49
  sorted_numbers = sorted(numbers)
50
  length = len(sorted_numbers)
51
+
52
  if length % 2 == 0:
53
  mid = length // 2
54
  return (sorted_numbers[mid - 1] + sorted_numbers[mid]) / 2
55
  else:
56
+ return sorted_numbers[length // 2]