ABDALLALSWAITI commited on
Commit
2e0fe32
·
verified ·
1 Parent(s): c8a2b09

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -38
app.py CHANGED
@@ -8,16 +8,102 @@ import shutil
8
  import base64
9
  import logging
10
  from typing import List, Optional
 
11
 
12
  # Configure logging
13
- logging.basicConfig(level=logging.INFO)
 
 
 
14
  logger = logging.getLogger(__name__)
15
 
16
- # Create FastAPI app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  app = FastAPI(
18
  title="HTML to PDF Converter API",
19
  description="Convert HTML to PDF with page breaks and image support",
20
- version="1.0.0"
 
21
  )
22
 
23
  # Add CORS middleware
@@ -33,7 +119,9 @@ app.add_middleware(
33
  async def root():
34
  """Root endpoint - returns immediately for health check"""
35
  return {
36
- "status": "online",
 
 
37
  "service": "HTML to PDF Converter API",
38
  "version": "1.0.0",
39
  "docs": "/docs",
@@ -43,37 +131,49 @@ async def root():
43
  @app.get("/health")
44
  async def health():
45
  """Health check endpoint"""
46
- return {"status": "healthy"}
 
 
47
 
48
  @app.get("/ui", response_class=HTMLResponse)
49
  async def ui():
50
  """UI documentation page"""
51
- return """
 
 
 
 
 
 
 
 
 
 
52
  <!DOCTYPE html>
53
  <html>
54
  <head>
55
  <title>HTML to PDF API</title>
56
  <meta name="viewport" content="width=device-width, initial-scale=1">
57
  <style>
58
- * { margin: 0; padding: 0; box-sizing: border-box; }
59
- body {
60
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
61
  line-height: 1.6;
62
  color: #333;
63
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
64
  min-height: 100vh;
65
  padding: 20px;
66
- }
67
- .container {
68
  max-width: 900px;
69
  margin: 0 auto;
70
  background: white;
71
  border-radius: 10px;
72
  padding: 40px;
73
  box-shadow: 0 10px 40px rgba(0,0,0,0.2);
74
- }
75
- h1 { color: #667eea; margin-bottom: 10px; font-size: 2.5em; }
76
- .status {
77
  display: inline-block;
78
  background: #10b981;
79
  color: white;
@@ -81,34 +181,29 @@ async def ui():
81
  border-radius: 20px;
82
  font-size: 0.9em;
83
  margin-bottom: 30px;
84
- }
85
- h2 { color: #333; margin: 30px 0 15px; border-bottom: 2px solid #667eea; padding-bottom: 10px; }
86
- .endpoint {
 
 
 
87
  background: #f8f9fa;
88
  border-left: 4px solid #667eea;
89
  padding: 20px;
90
  margin: 15px 0;
91
  border-radius: 5px;
92
- }
93
- .endpoint h3 { color: #667eea; margin-bottom: 10px; }
94
- code {
95
  background: #e9ecef;
96
  padding: 3px 8px;
97
  border-radius: 4px;
98
  font-family: 'Courier New', monospace;
99
  font-size: 0.9em;
100
- }
101
- pre {
102
- background: #2d3748;
103
- color: #fff;
104
- padding: 15px;
105
- border-radius: 5px;
106
- overflow-x: auto;
107
- margin: 10px 0;
108
- }
109
- ul { margin-left: 20px; }
110
- li { margin: 8px 0; }
111
- .btn {
112
  display: inline-block;
113
  background: #667eea;
114
  color: white;
@@ -117,18 +212,20 @@ async def ui():
117
  text-decoration: none;
118
  margin: 5px;
119
  transition: all 0.3s;
120
- }
121
- .btn:hover {
122
  background: #5568d3;
123
  transform: translateY(-2px);
124
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
125
- }
126
  </style>
127
  </head>
128
  <body>
129
  <div class="container">
130
  <h1>📄 HTML to PDF Converter API</h1>
131
- <span class="status">✓ Online</span>
 
 
132
 
133
  <p style="margin: 20px 0; font-size: 1.1em;">
134
  A powerful API to convert HTML documents to PDF with support for
@@ -214,6 +311,9 @@ def inject_page_css(html: str, ratio: str) -> str:
214
 
215
  def convert_to_pdf(html: str, ratio: str) -> bytes:
216
  """Convert HTML to PDF using Puppeteer"""
 
 
 
217
  temp_dir = tempfile.mkdtemp()
218
  try:
219
  html = inject_page_css(html, ratio)
@@ -228,18 +328,18 @@ def convert_to_pdf(html: str, ratio: str) -> bytes:
228
  ['node', '/app/puppeteer_pdf.js', html_file, ratio],
229
  capture_output=True,
230
  text=True,
231
- timeout=60,
232
  cwd='/app'
233
  )
234
 
235
  if result.returncode != 0:
236
  logger.error(f"Conversion failed: {result.stderr}")
237
- raise Exception(f"PDF conversion failed: {result.stderr}")
238
 
239
  pdf_file = html_file.replace('.html', '.pdf')
240
 
241
  if not os.path.exists(pdf_file):
242
- raise Exception("PDF file was not generated")
243
 
244
  with open(pdf_file, 'rb') as f:
245
  pdf_bytes = f.read()
@@ -247,6 +347,12 @@ def convert_to_pdf(html: str, ratio: str) -> bytes:
247
  logger.info(f"PDF generated: {len(pdf_bytes)} bytes")
248
  return pdf_bytes
249
 
 
 
 
 
 
 
250
  finally:
251
  shutil.rmtree(temp_dir, ignore_errors=True)
252
 
@@ -290,6 +396,8 @@ async def convert(
290
  }
291
  )
292
 
 
 
293
  except Exception as e:
294
  logger.error(f"Error: {str(e)}")
295
  raise HTTPException(500, str(e))
@@ -321,6 +429,8 @@ async def convert_html(
321
  }
322
  )
323
 
 
 
324
  except Exception as e:
325
  logger.error(f"Error: {str(e)}")
326
  raise HTTPException(500, str(e))
@@ -351,6 +461,8 @@ async def convert_base64(
351
  "size_bytes": len(pdf)
352
  })
353
 
 
 
354
  except Exception as e:
355
  logger.error(f"Error: {str(e)}")
356
  raise HTTPException(500, str(e))
 
8
  import base64
9
  import logging
10
  from typing import List, Optional
11
+ from contextlib import asynccontextmanager
12
 
13
  # Configure logging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17
+ )
18
  logger = logging.getLogger(__name__)
19
 
20
+ # Global state
21
+ app_state = {"ready": False, "error": None}
22
+
23
+ @asynccontextmanager
24
+ async def lifespan(app: FastAPI):
25
+ """Startup and shutdown events"""
26
+ logger.info("=" * 60)
27
+ logger.info("HTML to PDF Converter API Starting...")
28
+ logger.info("=" * 60)
29
+ logger.info("Checking dependencies...")
30
+
31
+ try:
32
+ # Check Node.js
33
+ node_version = subprocess.run(
34
+ ['node', '--version'],
35
+ capture_output=True,
36
+ text=True,
37
+ timeout=5
38
+ )
39
+ logger.info(f"Node.js version: {node_version.stdout.strip()}")
40
+
41
+ # Check Chromium
42
+ chromium_path = os.environ.get('PUPPETEER_EXECUTABLE_PATH', '/usr/bin/chromium')
43
+ if os.path.exists(chromium_path):
44
+ logger.info(f"Chromium found at: {chromium_path}")
45
+ else:
46
+ logger.error(f"Chromium NOT found at: {chromium_path}")
47
+ app_state["error"] = f"Chromium not found at {chromium_path}"
48
+
49
+ # Check Puppeteer script
50
+ puppeteer_script = '/app/puppeteer_pdf.js'
51
+ if os.path.exists(puppeteer_script):
52
+ logger.info("Puppeteer script found")
53
+ else:
54
+ logger.error(f"Puppeteer script NOT found at {puppeteer_script}")
55
+ app_state["error"] = f"Puppeteer script not found"
56
+
57
+ # Test Puppeteer
58
+ logger.info("Testing Puppeteer...")
59
+ test_html = "<html><body><h1>Test</h1></body></html>"
60
+ test_dir = tempfile.mkdtemp()
61
+ try:
62
+ test_file = os.path.join(test_dir, "test.html")
63
+ with open(test_file, 'w') as f:
64
+ f.write(test_html)
65
+
66
+ result = subprocess.run(
67
+ ['node', puppeteer_script, test_file, '9:16'],
68
+ capture_output=True,
69
+ text=True,
70
+ timeout=30,
71
+ cwd='/app'
72
+ )
73
+
74
+ if result.returncode == 0:
75
+ logger.info("Puppeteer test PASSED ✓")
76
+ app_state["ready"] = True
77
+ else:
78
+ logger.error(f"Puppeteer test FAILED: {result.stderr}")
79
+ app_state["error"] = f"Puppeteer test failed: {result.stderr}"
80
+ except Exception as e:
81
+ logger.error(f"Puppeteer test FAILED with exception: {e}")
82
+ app_state["error"] = str(e)
83
+ finally:
84
+ shutil.rmtree(test_dir, ignore_errors=True)
85
+
86
+ logger.info("=" * 60)
87
+ if app_state["ready"]:
88
+ logger.info("API is ready!")
89
+ else:
90
+ logger.error(f"API NOT ready: {app_state.get('error', 'Unknown error')}")
91
+ logger.info("=" * 60)
92
+
93
+ except Exception as e:
94
+ logger.error(f"Startup check failed: {e}")
95
+ app_state["error"] = str(e)
96
+
97
+ yield
98
+
99
+ logger.info("Shutting down...")
100
+
101
+ # Create FastAPI app with lifespan
102
  app = FastAPI(
103
  title="HTML to PDF Converter API",
104
  description="Convert HTML to PDF with page breaks and image support",
105
+ version="1.0.0",
106
+ lifespan=lifespan
107
  )
108
 
109
  # Add CORS middleware
 
119
  async def root():
120
  """Root endpoint - returns immediately for health check"""
121
  return {
122
+ "status": "online" if app_state["ready"] else "error",
123
+ "ready": app_state["ready"],
124
+ "error": app_state.get("error"),
125
  "service": "HTML to PDF Converter API",
126
  "version": "1.0.0",
127
  "docs": "/docs",
 
131
  @app.get("/health")
132
  async def health():
133
  """Health check endpoint"""
134
+ if not app_state["ready"]:
135
+ raise HTTPException(503, f"Service not ready: {app_state.get('error', 'Unknown error')}")
136
+ return {"status": "healthy", "ready": True}
137
 
138
  @app.get("/ui", response_class=HTMLResponse)
139
  async def ui():
140
  """UI documentation page"""
141
+ status_class = "online" if app_state["ready"] else "error"
142
+ status_text = "✓ Online" if app_state["ready"] else "✗ Error"
143
+ error_html = ""
144
+ if not app_state["ready"]:
145
+ error_html = f"""
146
+ <div style="background: #fee; border: 2px solid #c00; padding: 15px; margin: 20px 0; border-radius: 5px;">
147
+ <strong>⚠️ Service Error:</strong> {app_state.get('error', 'Unknown error')}
148
+ </div>
149
+ """
150
+
151
+ return f"""
152
  <!DOCTYPE html>
153
  <html>
154
  <head>
155
  <title>HTML to PDF API</title>
156
  <meta name="viewport" content="width=device-width, initial-scale=1">
157
  <style>
158
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
159
+ body {{
160
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
161
  line-height: 1.6;
162
  color: #333;
163
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
164
  min-height: 100vh;
165
  padding: 20px;
166
+ }}
167
+ .container {{
168
  max-width: 900px;
169
  margin: 0 auto;
170
  background: white;
171
  border-radius: 10px;
172
  padding: 40px;
173
  box-shadow: 0 10px 40px rgba(0,0,0,0.2);
174
+ }}
175
+ h1 {{ color: #667eea; margin-bottom: 10px; font-size: 2.5em; }}
176
+ .status {{
177
  display: inline-block;
178
  background: #10b981;
179
  color: white;
 
181
  border-radius: 20px;
182
  font-size: 0.9em;
183
  margin-bottom: 30px;
184
+ }}
185
+ .status.error {{
186
+ background: #ef4444;
187
+ }}
188
+ h2 {{ color: #333; margin: 30px 0 15px; border-bottom: 2px solid #667eea; padding-bottom: 10px; }}
189
+ .endpoint {{
190
  background: #f8f9fa;
191
  border-left: 4px solid #667eea;
192
  padding: 20px;
193
  margin: 15px 0;
194
  border-radius: 5px;
195
+ }}
196
+ .endpoint h3 {{ color: #667eea; margin-bottom: 10px; }}
197
+ code {{
198
  background: #e9ecef;
199
  padding: 3px 8px;
200
  border-radius: 4px;
201
  font-family: 'Courier New', monospace;
202
  font-size: 0.9em;
203
+ }}
204
+ ul {{ margin-left: 20px; }}
205
+ li {{ margin: 8px 0; }}
206
+ .btn {{
 
 
 
 
 
 
 
 
207
  display: inline-block;
208
  background: #667eea;
209
  color: white;
 
212
  text-decoration: none;
213
  margin: 5px;
214
  transition: all 0.3s;
215
+ }}
216
+ .btn:hover {{
217
  background: #5568d3;
218
  transform: translateY(-2px);
219
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
220
+ }}
221
  </style>
222
  </head>
223
  <body>
224
  <div class="container">
225
  <h1>📄 HTML to PDF Converter API</h1>
226
+ <span class="status {status_class}">{status_text}</span>
227
+
228
+ {error_html}
229
 
230
  <p style="margin: 20px 0; font-size: 1.1em;">
231
  A powerful API to convert HTML documents to PDF with support for
 
311
 
312
  def convert_to_pdf(html: str, ratio: str) -> bytes:
313
  """Convert HTML to PDF using Puppeteer"""
314
+ if not app_state["ready"]:
315
+ raise HTTPException(503, f"Service not ready: {app_state.get('error', 'Unknown')}")
316
+
317
  temp_dir = tempfile.mkdtemp()
318
  try:
319
  html = inject_page_css(html, ratio)
 
328
  ['node', '/app/puppeteer_pdf.js', html_file, ratio],
329
  capture_output=True,
330
  text=True,
331
+ timeout=50, # Reduced from 60 to give buffer
332
  cwd='/app'
333
  )
334
 
335
  if result.returncode != 0:
336
  logger.error(f"Conversion failed: {result.stderr}")
337
+ raise HTTPException(500, f"PDF conversion failed: {result.stderr}")
338
 
339
  pdf_file = html_file.replace('.html', '.pdf')
340
 
341
  if not os.path.exists(pdf_file):
342
+ raise HTTPException(500, "PDF file was not generated")
343
 
344
  with open(pdf_file, 'rb') as f:
345
  pdf_bytes = f.read()
 
347
  logger.info(f"PDF generated: {len(pdf_bytes)} bytes")
348
  return pdf_bytes
349
 
350
+ except subprocess.TimeoutExpired:
351
+ logger.error("PDF conversion timed out")
352
+ raise HTTPException(504, "PDF conversion timed out. Try a simpler HTML document.")
353
+ except Exception as e:
354
+ logger.error(f"Conversion error: {str(e)}")
355
+ raise HTTPException(500, str(e))
356
  finally:
357
  shutil.rmtree(temp_dir, ignore_errors=True)
358
 
 
396
  }
397
  )
398
 
399
+ except HTTPException:
400
+ raise
401
  except Exception as e:
402
  logger.error(f"Error: {str(e)}")
403
  raise HTTPException(500, str(e))
 
429
  }
430
  )
431
 
432
+ except HTTPException:
433
+ raise
434
  except Exception as e:
435
  logger.error(f"Error: {str(e)}")
436
  raise HTTPException(500, str(e))
 
461
  "size_bytes": len(pdf)
462
  })
463
 
464
+ except HTTPException:
465
+ raise
466
  except Exception as e:
467
  logger.error(f"Error: {str(e)}")
468
  raise HTTPException(500, str(e))