validops-east-1 commited on
Commit
a952141
·
1 Parent(s): 91463a0

feat: updated code

Browse files
.gitignore CHANGED
@@ -124,4 +124,5 @@ Pipfile.lock
124
  run_docker.py
125
  docs
126
  .claude
127
- start_docker.txt
 
 
124
  run_docker.py
125
  docs
126
  .claude
127
+ start_docker.txt
128
+ Reconciliation-file-processing-service.postman_collection.json
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: LLM Ready Data API — v2.1.0
3
  emoji: ⚡
4
  colorFrom: green
5
  colorTo: purple
 
1
  ---
2
+ title: reconciliation-file-processing-service
3
  emoji: ⚡
4
  colorFrom: green
5
  colorTo: purple
app/__init__.py ADDED
File without changes
app/core/__init__.py ADDED
File without changes
app/core/config/__init__.py ADDED
File without changes
app/core/config/settings.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Application settings via Pydantic Settings.
2
+
3
+ Configuration is driven by environment variables with sensible defaults
4
+ for local development. In production, set these via the container's
5
+ environment (Docker Compose, K8s ConfigMap/Secret, etc.).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+
13
+ class Settings:
14
+ """Simple settings container — no external dependencies required."""
15
+
16
+ SERVICE_NAME: str = "reconciliation-file-processing-service"
17
+ API_VERSION: str = "2.1.0"
18
+ ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development")
19
+
20
+
21
+ _settings: Settings | None = None
22
+
23
+
24
+ def get_settings() -> Settings:
25
+ global _settings
26
+ if _settings is None:
27
+ _settings = Settings()
28
+ return _settings
banner.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pyfiglet
2
+ from rich.console import Console
3
+ from rich.rule import Rule
4
+ from rich.text import Text
5
+
6
+ from app.core.config.settings import get_settings
7
+
8
+ _console = Console()
9
+
10
+
11
+ def print_banner() -> None:
12
+ settings = get_settings()
13
+
14
+ art = pyfiglet.figlet_format("ValidOps", font="slant")
15
+
16
+ _console.print(Text(art, style="bold cyan"))
17
+ _console.print(f" [bold white]{'Service:':<14}[/bold white] [cyan]{settings.SERVICE_NAME}[/cyan]")
18
+ _console.print(f" [bold white]{'Version:':<14}[/bold white] [cyan]v{settings.API_VERSION}[/cyan]")
19
+ _console.print(f" [bold white]{'Environment:':<14}[/bold white] [cyan]{settings.ENVIRONMENT}[/cyan]")
20
+ _console.print(Rule(style="dim cyan"))
21
+ _console.print()
logger.py CHANGED
@@ -1,136 +1,87 @@
1
- """
2
- Logging configuration for the MarkItDown API.
 
 
3
 
4
- Provides a structured plaintext formatter suitable for production log
5
- aggregation pipelines (stdout + optional log file). No ANSI colour codes
6
- are emitted, keeping output clean for log collectors and CI environments.
7
 
8
- Public surface
9
- --------------
10
- get_logger(name) -> logging.Logger
11
- Returns the named logger, bootstrapping from the root application
12
- logger if the logger has not been configured yet.
13
 
14
- setup_logger(...) -> logging.Logger
15
- Full configuration path used once at application startup.
16
 
17
- app_logger
18
- Pre-configured root logger instance used by all modules.
19
  """
20
 
21
  from __future__ import annotations
22
 
 
23
  import logging
 
24
  import sys
25
- from pathlib import Path
26
- from typing import Optional
27
 
28
 
29
- # ---------------------------------------------------------------------------
30
- # Formatter
31
- # ---------------------------------------------------------------------------
32
 
33
- class PlainFormatter(logging.Formatter):
34
- """Structured pipe-delimited log line.
35
 
36
- Example output::
37
- 2024-01-15 12:34:56 | INFO | llm_ready_data_extractor.api.server | message text
 
 
 
 
38
  """
39
 
 
 
40
  def format(self, record: logging.LogRecord) -> str:
41
- timestamp = self.formatTime(record, self.datefmt)
42
- line = (
43
- f"{timestamp} | {record.levelname:<8} | "
44
- f"{record.name} | {record.getMessage()}"
45
- )
46
- if record.exc_info:
47
- line = f"{line}\n{self.formatException(record.exc_info)}"
48
- return line
49
-
50
-
51
- # ---------------------------------------------------------------------------
52
- # Handler factories
53
- # ---------------------------------------------------------------------------
54
-
55
- def _build_console_handler() -> logging.StreamHandler:
56
- """Return a StreamHandler writing structured lines to stdout."""
57
- handler = logging.StreamHandler(sys.stdout)
58
- handler.setFormatter(
59
- PlainFormatter(datefmt="%Y-%m-%d %H:%M:%S")
60
- )
61
- return handler
62
-
63
-
64
- def _build_file_handler(log_file: str) -> logging.FileHandler:
65
- """Return a FileHandler writing to *log_file*, creating parent dirs as needed."""
66
- log_path = Path(log_file)
67
- log_path.parent.mkdir(parents=True, exist_ok=True)
68
- handler = logging.FileHandler(log_file, encoding="utf-8")
69
- handler.setFormatter(
70
- PlainFormatter(datefmt="%Y-%m-%d %H:%M:%S")
71
- )
72
- return handler
73
-
74
-
75
- # ---------------------------------------------------------------------------
76
- # Public API
77
- # ---------------------------------------------------------------------------
78
-
79
- def setup_logger(
80
- name: str,
81
- level: str = "INFO",
82
- log_file: Optional[str] = None,
83
- enable_console: bool = True,
84
- ) -> logging.Logger:
85
- """Configure and return a named logger.
86
-
87
- Parameters
88
- ----------
89
- name:
90
- Logger name — pass ``__name__`` from the calling module.
91
- level:
92
- Minimum log level string, e.g. ``"DEBUG"``, ``"INFO"``, ``"WARNING"``.
93
- log_file:
94
- Optional path to a persistent log file. When supplied, records are
95
- written to both stdout and the file.
96
- enable_console:
97
- Set to ``False`` to suppress stdout output (useful in test environments).
98
  """
99
- logger = logging.getLogger(name)
100
- logger.setLevel(getattr(logging, level.upper(), logging.INFO))
101
- logger.handlers.clear()
102
- logger.propagate = False
103
 
104
- if enable_console:
105
- logger.addHandler(_build_console_handler())
106
 
107
- if log_file:
108
- logger.addHandler(_build_file_handler(log_file))
109
 
110
- return logger
111
 
 
 
 
 
 
 
112
 
113
- def get_logger(name: str) -> logging.Logger:
114
- """Return the named logger, configuring it with defaults if not yet set up.
115
 
116
- Intended usage in application modules::
 
117
 
118
- from logger import get_logger
119
- logger = get_logger(__name__)
120
- """
121
- existing = logging.getLogger(name)
122
- if existing.handlers:
123
- return existing
124
- return setup_logger(name)
125
-
126
-
127
- # ---------------------------------------------------------------------------
128
- # Root application logger — configured once at import time
129
- # ---------------------------------------------------------------------------
130
-
131
- app_logger = setup_logger(
132
- "llm_ready_data_extractor",
133
- level="INFO",
134
- log_file="logs/markitdown.log",
135
- enable_console=True,
136
- )
 
1
+ """Production-grade structured JSON logger.
2
+
3
+ Emits newline-delimited JSON to stdout (12-factor app pattern) for
4
+ seamless ingestion by log aggregators (Datadog, ELK, CloudWatch, etc.).
5
 
6
+ Usage
7
+ -----
8
+ from logger import get_logger
9
 
10
+ logger = get_logger(__name__)
11
+ logger.info("processed | file=%s | rows=%d", filename, n)
 
 
 
12
 
13
+ Configuration (environment variables):
 
14
 
15
+ LOG_LEVEL Minimum level (default: INFO)
 
16
  """
17
 
18
  from __future__ import annotations
19
 
20
+ import json
21
  import logging
22
+ import os
23
  import sys
24
+ from datetime import datetime, timezone
25
+ from typing import Any
26
 
27
 
28
+ __all__ = ["get_logger", "app_logger"]
 
 
29
 
 
 
30
 
31
+ class JSONFormatter(logging.Formatter):
32
+ """Render log records as JSON lines.
33
+
34
+ Each line contains ``timestamp`` (ISO 8601 UTC), ``level``,
35
+ ``logger``, and ``message``. Exception tracebacks are included
36
+ under ``exception`` when ``exc_info`` is set on the record.
37
  """
38
 
39
+ __slots__ = ()
40
+
41
  def format(self, record: logging.LogRecord) -> str:
42
+ payload: dict[str, Any] = {
43
+ "timestamp": datetime.fromtimestamp(
44
+ record.created, tz=timezone.utc
45
+ ).isoformat(),
46
+ "level": record.levelname,
47
+ "logger": record.name,
48
+ "message": record.getMessage(),
49
+ }
50
+ if record.exc_info and record.exc_info[0] is not None:
51
+ payload["exception"] = self.formatException(record.exc_info)
52
+ return json.dumps(payload, default=str, ensure_ascii=False, sort_keys=True)
53
+
54
+
55
+ def get_logger(name: str) -> logging.Logger:
56
+ """Return a named logger.
57
+
58
+ All loggers inherit handlers from the root logger, which is
59
+ configured once at import time with a :class:`JSONFormatter`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  """
61
+ return logging.getLogger(name)
 
 
 
62
 
 
 
63
 
64
+ _initialised: bool = False
 
65
 
 
66
 
67
+ def _setup() -> None:
68
+ """Configure the root logger once from environment defaults."""
69
+ global _initialised
70
+ if _initialised:
71
+ return
72
+ _initialised = True
73
 
74
+ level = os.getenv("LOG_LEVEL", "INFO").upper()
 
75
 
76
+ root = logging.getLogger()
77
+ root.setLevel(getattr(logging, level, logging.INFO))
78
 
79
+ fmt = JSONFormatter()
80
+
81
+ console = logging.StreamHandler(sys.stdout)
82
+ console.setFormatter(fmt)
83
+ root.addHandler(console)
84
+
85
+
86
+ _setup()
87
+ app_logger = get_logger("docx")
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -10,11 +10,13 @@ from __future__ import annotations
10
 
11
  import uvicorn
12
 
 
13
  from logger import get_logger
14
 
15
  logger = get_logger(__name__)
16
 
17
  if __name__ == "__main__":
 
18
  logger.info("Starting MarkItDown API server")
19
  uvicorn.run(
20
  "api.server:app",
 
10
 
11
  import uvicorn
12
 
13
+ from banner import print_banner
14
  from logger import get_logger
15
 
16
  logger = get_logger(__name__)
17
 
18
  if __name__ == "__main__":
19
+ print_banner()
20
  logger.info("Starting MarkItDown API server")
21
  uvicorn.run(
22
  "api.server:app",
requirements.txt CHANGED
@@ -19,3 +19,7 @@ pandas>=2.0.0
19
 
20
  # spaCy NER extraction for non-tabular file formats
21
  spacy>=3.7.0
 
 
 
 
 
19
 
20
  # spaCy NER extraction for non-tabular file formats
21
  spacy>=3.7.0
22
+
23
+ # CLI banner
24
+ pyfiglet>=1.0.2
25
+ rich>=13.0.0