AkashKumarave commited on
Commit
cf6e26f
·
verified ·
1 Parent(s): ddf1d25

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +22 -659
app.py CHANGED
@@ -1,670 +1,33 @@
 
1
  import os
2
- import json
3
  import requests
4
- import traceback
5
- from flask import Flask, request, jsonify
6
- from bs4 import BeautifulSoup
7
- import logging
8
- import cssutils
9
- import re
10
- import urllib.parse
11
- from PIL import Image
12
- from io import BytesIO
13
 
14
  app = Flask(__name__)
15
 
16
- # Setup logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
- cssutils.log.setLevel(logging.CRITICAL) # Suppress CSS parsing warnings
20
-
21
- @app.route('/')
22
- def home():
23
- return """
24
- <!DOCTYPE html>
25
- <html>
26
- <head>
27
- <title>Website Converter</title>
28
- </head>
29
- <body>
30
- <h1>Welcome to Website Converter</h1>
31
- <p>Use the /api/convert endpoint to convert websites to structured data.</p>
32
- </body>
33
- </html>
34
- """
35
-
36
- @app.route('/api/convert', methods=['POST'])
37
- def convert_website():
38
- try:
39
- data = request.json
40
- if not data:
41
- return jsonify({"error": "No data provided"}), 400
42
-
43
- url = data.get('url')
44
- if not url:
45
- return jsonify({"error": "URL is required"}), 400
46
-
47
- # Add http if not present
48
- if not url.startswith('http'):
49
- url = 'https://' + url
50
-
51
- viewport_width = int(data.get('viewport_width', 1440))
52
- viewport_height = 900 # Default height
53
-
54
- logger.info(f"Converting website: {url} with viewport width: {viewport_width}")
55
-
56
- try:
57
- # Use requests to get the webpage
58
- headers = {
59
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
60
- 'Accept': 'text/html,application/xhtml+xml,application/xml',
61
- 'Accept-Language': 'en-US,en;q=0.9',
62
- }
63
-
64
- response = requests.get(url, headers=headers, timeout=20)
65
- response.raise_for_status() # Raise an exception for HTTP errors
66
-
67
- html_content = response.text
68
-
69
- # Parse the HTML content
70
- soup = BeautifulSoup(html_content, 'html.parser')
71
-
72
- # Extract all CSS styles (improved method)
73
- all_styles = extract_all_css(soup, url)
74
-
75
- # Extract the page elements using BeautifulSoup
76
- elements = extract_elements_improved(soup, all_styles)
77
-
78
- # Estimate page height based on content
79
- estimated_height = viewport_height
80
- if elements:
81
- # Find the maximum y-coordinate plus height
82
- max_y = 0
83
- for element in elements:
84
- elem_bottom = element.get('y', 0) + element.get('height', 0)
85
- max_y = max(max_y, elem_bottom)
86
- estimated_height = max(viewport_height, max_y)
87
-
88
- # Prepare response
89
- response = {
90
- "status": "success",
91
- "url": url,
92
- "viewport_width": viewport_width,
93
- "viewport_height": estimated_height,
94
- "elements": elements
95
- }
96
-
97
- return jsonify(response)
98
-
99
- except requests.exceptions.RequestException as e:
100
- logger.error(f"Request error: {str(e)}")
101
- return jsonify({"error": f"Failed to fetch website: {str(e)}"}), 500
102
-
103
- except Exception as e:
104
- logger.error(f"Error: {str(e)}")
105
- logger.error(traceback.format_exc())
106
- return jsonify({"error": str(e), "traceback": traceback.format_exc()}), 500
107
-
108
- def extract_all_css(soup, base_url):
109
- """Extract all CSS from the page: inline, style tags, and external stylesheets"""
110
- all_styles = {}
111
-
112
- # 1. Extract inline styles
113
- for element in soup.find_all(style=True):
114
- element_id = element.get('id')
115
- element_classes = element.get('class', [])
116
-
117
- # Create selectors for this element
118
- selectors = []
119
- if element_id:
120
- selectors.append(f"#{element_id}")
121
- if element_classes:
122
- for cls in element_classes:
123
- selectors.append(f".{cls}")
124
- if not selectors: # Fallback to tag name
125
- selectors.append(element.name)
126
-
127
- # Store inline style for each selector
128
- inline_style = parse_inline_style(element['style'])
129
- for selector in selectors:
130
- all_styles[selector] = inline_style
131
-
132
- # 2. Extract style tags
133
- for style_tag in soup.find_all('style'):
134
- if style_tag.string:
135
- css_dict = parse_css(style_tag.string)
136
- all_styles.update(css_dict)
137
-
138
- # 3. Extract linked stylesheets
139
- for link in soup.find_all('link', rel='stylesheet'):
140
- href = link.get('href')
141
- if not href:
142
- continue
143
-
144
- # Make absolute URL if relative
145
- if not href.startswith(('http://', 'https://')):
146
- href = urllib.parse.urljoin(base_url, href)
147
-
148
- try:
149
- css_response = requests.get(href, timeout=10)
150
- if css_response.ok:
151
- css_dict = parse_css(css_response.text)
152
- all_styles.update(css_dict)
153
- except Exception as e:
154
- logger.warning(f"Failed to fetch stylesheet {href}: {e}")
155
-
156
- # 4. Add computed styles for common elements
157
- add_default_styles(all_styles)
158
-
159
- return all_styles
160
-
161
- def parse_inline_style(style_text):
162
- """Parse inline style string into a dictionary"""
163
- style_dict = {}
164
- if not style_text:
165
- return style_dict
166
-
167
- # Split style string into individual properties
168
- for item in style_text.split(';'):
169
- if ':' in item:
170
- prop, value = item.split(':', 1)
171
- prop = prop.strip().lower()
172
- value = value.strip()
173
- if prop and value:
174
- style_dict[prop] = value
175
-
176
- return style_dict
177
 
178
- def parse_css(css_text):
179
- """Parse CSS text into a dictionary of selectors and styles"""
180
- styles = {}
 
181
 
182
  try:
183
- sheet = cssutils.parseString(css_text)
184
- for rule in sheet:
185
- # Only handle style rules (not @media, etc.)
186
- if rule.type == rule.STYLE_RULE:
187
- selector = rule.selectorText
188
- style_dict = {}
189
-
190
- for prop in rule.style:
191
- if prop.name and prop.value:
192
- style_dict[prop.name.lower()] = prop.value
193
-
194
- # Add to styles, merging if selector already exists
195
- if selector in styles:
196
- styles[selector].update(style_dict)
197
- else:
198
- styles[selector] = style_dict
199
- except Exception as e:
200
- logger.warning(f"CSS parsing error: {e}")
201
-
202
- return styles
203
-
204
- def add_default_styles(styles):
205
- """Add default styles for common HTML elements"""
206
- # Body defaults
207
- styles.setdefault('body', {}).update({
208
- 'margin': '0px',
209
- 'font-family': 'Arial, sans-serif',
210
- 'color': '#000000',
211
- 'font-size': '16px'
212
- })
213
-
214
- # Heading defaults
215
- styles.setdefault('h1', {}).update({'font-size': '32px', 'font-weight': 'bold', 'margin': '21.44px 0'})
216
- styles.setdefault('h2', {}).update({'font-size': '24px', 'font-weight': 'bold', 'margin': '19.92px 0'})
217
- styles.setdefault('h3', {}).update({'font-size': '18px', 'font-weight': 'bold', 'margin': '18.72px 0'})
218
- styles.setdefault('h4', {}).update({'font-size': '16px', 'font-weight': 'bold', 'margin': '21.28px 0'})
219
-
220
- # Link defaults
221
- styles.setdefault('a', {}).update({'color': '#0000EE', 'text-decoration': 'underline'})
222
-
223
- # Button defaults
224
- styles.setdefault('button', {}).update({
225
- 'background-color': '#F0F0F0',
226
- 'border': '1px solid #CCCCCC',
227
- 'padding': '4px 8px',
228
- 'border-radius': '2px'
229
- })
230
-
231
- # Input defaults
232
- styles.setdefault('input', {}).update({
233
- 'border': '1px solid #CCCCCC',
234
- 'padding': '2px 4px'
235
- })
236
-
237
- def extract_elements_improved(soup, styles):
238
- """Extract elements from the webpage with improved CSS handling"""
239
- elements = []
240
-
241
- # Get the body element
242
- body = soup.find('body')
243
- if not body:
244
- return elements
245
-
246
- # Start position for elements
247
- x_offset = 0
248
- y_position = 0
249
- viewport_width = 1440 # Default width
250
-
251
- # Create a mapping of elements to their computed styles
252
- element_styles = {}
253
-
254
- # Process main content blocks first
255
- main_blocks = body.find_all(['div', 'header', 'main', 'nav', 'footer', 'section'], recursive=False)
256
-
257
- if not main_blocks: # If no main blocks, use all direct children
258
- main_blocks = body.find_all(recursive=False)
259
-
260
- # Process each main block
261
- for block in main_blocks:
262
- block_data = process_element_with_styles(block, x_offset, y_position, viewport_width, styles)
263
- if block_data:
264
- elements.append(block_data)
265
- y_position += block_data['height'] + 10 # Add spacing between blocks
266
-
267
- # If no elements were found, try to extract text directly
268
- if not elements and body.text.strip():
269
- text_element = {
270
- 'type': 'text',
271
- 'tagName': 'p',
272
- 'x': 0,
273
- 'y': 0,
274
- 'width': viewport_width,
275
- 'height': 100,
276
- 'content': body.text.strip(),
277
- 'style': {
278
- 'color': '#000000',
279
- 'fontSize': '16px',
280
- 'fontFamily': 'Arial, sans-serif'
281
  }
282
- }
283
- elements.append(text_element)
284
-
285
- return elements
286
-
287
- def process_element_with_styles(element, x_position, y_position, parent_width, styles, depth=0):
288
- """Process a single HTML element with its styles"""
289
- if depth > 10: # Limit recursion depth
290
- return None
291
-
292
- tag_name = element.name.lower() if hasattr(element, 'name') else None
293
- if not tag_name or tag_name in ['script', 'style', 'meta', 'link', 'noscript']:
294
- return None
295
-
296
- # Get element's classes and ID
297
- elem_classes = element.get('class', [])
298
- elem_id = element.get('id')
299
-
300
- # Calculate element's computed style
301
- computed_style = compute_element_style(element, tag_name, elem_id, elem_classes, styles)
302
-
303
- # Create base element data
304
- element_data = {
305
- 'type': get_element_type(tag_name),
306
- 'tagName': tag_name,
307
- 'x': x_position,
308
- 'y': y_position,
309
- 'width': calc_element_width(computed_style, parent_width),
310
- 'height': 50, # Default height, will be adjusted later
311
- 'style': {}
312
- }
313
-
314
- # Set element ID and class if present
315
- if elem_id:
316
- element_data['id'] = elem_id
317
-
318
- if elem_classes:
319
- if isinstance(elem_classes, list):
320
- element_data['className'] = ' '.join(elem_classes)
321
- else:
322
- element_data['className'] = elem_classes
323
-
324
- # Process specific element types
325
- if element_data['type'] == 'text':
326
- text_content = element.get_text().strip()
327
- element_data['content'] = text_content
328
-
329
- # Set text styles
330
- extract_text_styles(element_data, computed_style)
331
-
332
- # Calculate height based on text content
333
- element_data['height'] = calc_text_height(text_content, computed_style)
334
-
335
- elif element_data['type'] == 'image':
336
- # Set image source if available
337
- element_data['src'] = element.get('src', '')
338
- element_data['alt'] = element.get('alt', '')
339
-
340
- # Set height for images
341
- if 'height' in computed_style:
342
- try:
343
- element_data['height'] = parse_dimension(computed_style['height'], parent_width)
344
- except:
345
- element_data['height'] = 200 # Default height
346
- else:
347
- element_data['height'] = 200
348
-
349
- # Extract background styles
350
- extract_background_styles(element_data, computed_style)
351
-
352
- elif element_data['type'] in ['div', 'container', 'rectangle']:
353
- # Process container elements
354
- extract_container_styles(element_data, computed_style)
355
-
356
- # Process children
357
- children = []
358
- child_y_position = 0
359
- child_x_position = 0
360
-
361
- # Apply padding if present
362
- padding_left = parse_dimension(computed_style.get('padding-left', '0'), parent_width)
363
- child_x_position += padding_left
364
-
365
- available_width = element_data['width'] - (padding_left + parse_dimension(computed_style.get('padding-right', '0'), parent_width))
366
-
367
- # Process child elements
368
- for child in element.find_all(['div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'span', 'a', 'button', 'input', 'form'], recursive=False):
369
- child_data = process_element_with_styles(child, child_x_position, child_y_position, available_width, styles, depth + 1)
370
- if child_data:
371
- children.append(child_data)
372
- if 'display' in computed_style and computed_style['display'] == 'flex':
373
- # Handle flex layout (simplified)
374
- if computed_style.get('flex-direction') == 'row':
375
- child_x_position += child_data['width'] + 5
376
- else:
377
- child_y_position += child_data['height'] + 5
378
- else:
379
- # Default block layout
380
- child_y_position += child_data['height'] + 5
381
-
382
- if children:
383
- element_data['children'] = children
384
-
385
- # Adjust container height based on children
386
- if children and 'display' not in computed_style or computed_style.get('display') != 'flex':
387
- last_child = children[-1]
388
- element_data['height'] = last_child['y'] - element_data['y'] + last_child['height'] + 10
389
-
390
- # Apply common styles (border, margin, etc)
391
- apply_common_styles(element_data, computed_style)
392
-
393
- # If height is unreasonably small, set a minimum
394
- if element_data['height'] < 10:
395
- element_data['height'] = 10
396
-
397
- return element_data
398
-
399
- def get_element_type(tag_name):
400
- """Determine element type based on tag name"""
401
- if tag_name in ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'label']:
402
- return 'text'
403
- elif tag_name == 'img':
404
- return 'image'
405
- elif tag_name in ['div', 'section', 'article', 'header', 'footer', 'main', 'form']:
406
- return 'div'
407
- elif tag_name == 'button':
408
- return 'rectangle' # Represent as a rectangle with text
409
- elif tag_name == 'input':
410
- return 'rectangle' # Represent as a rectangle
411
- else:
412
- return 'div' # Default type
413
-
414
- def compute_element_style(element, tag_name, elem_id, elem_classes, styles):
415
- """Compute the final style for an element by cascading CSS rules"""
416
- computed_style = {}
417
-
418
- # 1. Apply tag-level styles
419
- if tag_name in styles:
420
- computed_style.update(styles[tag_name])
421
-
422
- # 2. Apply class styles
423
- if isinstance(elem_classes, list):
424
- for cls in elem_classes:
425
- class_selector = f".{cls}"
426
- if class_selector in styles:
427
- computed_style.update(styles[class_selector])
428
- elif elem_classes:
429
- class_selector = f".{elem_classes}"
430
- if class_selector in styles:
431
- computed_style.update(styles[class_selector])
432
-
433
- # 3. Apply ID styles (highest specificity)
434
- if elem_id and f"#{elem_id}" in styles:
435
- computed_style.update(styles[f"#{elem_id}"])
436
-
437
- # 4. Apply inline styles (overrides everything)
438
- inline_style = element.get('style')
439
- if inline_style:
440
- parsed_inline = parse_inline_style(inline_style)
441
- computed_style.update(parsed_inline)
442
-
443
- return computed_style
444
-
445
- def parse_dimension(value, container_size):
446
- """Parse dimension values (px, %, em, etc)"""
447
- if not value or not isinstance(value, str):
448
- return 0
449
-
450
- value = value.strip().lower()
451
-
452
- # Handle pixel values
453
- if value.endswith('px'):
454
- try:
455
- return float(value[:-2])
456
- except:
457
- return 0
458
-
459
- # Handle percentage values
460
- elif value.endswith('%'):
461
- try:
462
- percentage = float(value[:-1]) / 100
463
- return container_size * percentage
464
- except:
465
- return 0
466
-
467
- # Handle em values (approximate)
468
- elif value.endswith('em'):
469
- try:
470
- em_value = float(value[:-2])
471
- return em_value * 16 # Assuming 1em = 16px
472
- except:
473
- return 0
474
-
475
- # Handle rem values (approximate)
476
- elif value.endswith('rem'):
477
- try:
478
- rem_value = float(value[:-3])
479
- return rem_value * 16 # Assuming 1rem = 16px
480
- except:
481
- return 0
482
-
483
- # Handle vh/vw values (viewport height/width)
484
- elif value.endswith('vh'):
485
- try:
486
- vh_value = float(value[:-2]) / 100
487
- return vh_value * 900 # Assuming viewport height is 900px
488
- except:
489
- return 0
490
- elif value.endswith('vw'):
491
- try:
492
- vw_value = float(value[:-2]) / 100
493
- return vw_value * 1440 # Assuming viewport width is 1440px
494
- except:
495
- return 0
496
-
497
- # Handle numeric values
498
- elif value.isdigit():
499
- return float(value)
500
-
501
- # Handle auto (use container size)
502
- elif value == 'auto':
503
- return container_size
504
-
505
- # Default fallback
506
- return 0
507
-
508
- def calc_element_width(style, parent_width):
509
- """Calculate element width based on its style"""
510
- # Check if width is explicitly set
511
- if 'width' in style:
512
- width_value = style['width']
513
- return parse_dimension(width_value, parent_width)
514
-
515
- # Check for max-width
516
- if 'max-width' in style:
517
- max_width = parse_dimension(style['max-width'], parent_width)
518
- return min(parent_width, max_width)
519
-
520
- # Default: use parent width
521
- return parent_width
522
-
523
- def calc_text_height(text, style):
524
- """Calculate text height based on content and style"""
525
- if not text:
526
- return 20
527
-
528
- # Get font size
529
- font_size = 16 # Default
530
- if 'font-size' in style:
531
- font_size_value = style['font-size']
532
- if isinstance(font_size_value, str):
533
- if font_size_value.endswith('px'):
534
- try:
535
- font_size = float(font_size_value[:-2])
536
- except:
537
- pass
538
- elif font_size_value.endswith('em'):
539
- try:
540
- font_size = float(font_size_value[:-2]) * 16
541
- except:
542
- pass
543
-
544
- # Get line height
545
- line_height = 1.2 # Default
546
- if 'line-height' in style:
547
- line_height_value = style['line-height']
548
- if isinstance(line_height_value, str):
549
- if line_height_value.endswith('px'):
550
- try:
551
- line_height = float(line_height_value[:-2]) / font_size
552
- except:
553
- pass
554
- else:
555
- try:
556
- line_height = float(line_height_value)
557
- except:
558
- pass
559
-
560
- # Estimate number of lines needed
561
- text_length = len(text)
562
- chars_per_line = 70 # Rough estimate
563
- num_lines = max(1, (text_length / chars_per_line))
564
-
565
- # Calculate height
566
- return max(20, int(font_size * line_height * num_lines))
567
-
568
- def extract_text_styles(element_data, style):
569
- """Extract text-related styles from computed style"""
570
- # Text color
571
- if 'color' in style:
572
- element_data['style']['color'] = style['color']
573
- else:
574
- element_data['style']['color'] = '#000000' # Default black
575
-
576
- # Font size
577
- if 'font-size' in style:
578
- element_data['style']['fontSize'] = style['font-size']
579
- else:
580
- tag_name = element_data.get('tagName', '')
581
- if tag_name.startswith('h'):
582
- # Default heading sizes
583
- heading_level = int(tag_name[1])
584
- size = 32 - ((heading_level - 1) * 4)
585
- element_data['style']['fontSize'] = f"{size}px"
586
- else:
587
- element_data['style']['fontSize'] = '16px' # Default
588
-
589
- # Font weight
590
- if 'font-weight' in style:
591
- element_data['style']['fontWeight'] = style['font-weight']
592
- else:
593
- tag_name = element_data.get('tagName', '')
594
- if tag_name.startswith('h'):
595
- element_data['style']['fontWeight'] = 'bold'
596
- else:
597
- element_data['style']['fontWeight'] = 'normal'
598
-
599
- # Font family
600
- if 'font-family' in style:
601
- element_data['style']['fontFamily'] = style['font-family']
602
-
603
- # Text alignment
604
- if 'text-align' in style:
605
- element_data['style']['textAlign'] = style['text-align']
606
-
607
- # Text decoration
608
- if 'text-decoration' in style:
609
- element_data['style']['textDecoration'] = style['text-decoration']
610
-
611
- def extract_container_styles(element_data, style):
612
- """Extract container-related styles from computed style"""
613
- # Background color
614
- if 'background-color' in style:
615
- element_data['style']['backgroundColor'] = style['background-color']
616
-
617
- # Display type
618
- if 'display' in style:
619
- element_data['style']['display'] = style['display']
620
-
621
- # Flex-related properties
622
- if 'display' in style and style['display'] == 'flex':
623
- if 'flex-direction' in style:
624
- element_data['style']['flexDirection'] = style['flex-direction']
625
- if 'justify-content' in style:
626
- element_data['style']['justifyContent'] = style['justify-content']
627
- if 'align-items' in style:
628
- element_data['style']['alignItems'] = style['align-items']
629
-
630
- def extract_background_styles(element_data, style):
631
- """Extract background-related styles from computed style"""
632
- if 'background-color' in style:
633
- element_data['style']['backgroundColor'] = style['background-color']
634
-
635
- if 'background-image' in style:
636
- bg_image = style['background-image']
637
- if bg_image.startswith('url(') and bg_image.endswith(')'):
638
- image_url = bg_image[4:-1].strip('"\'')
639
- element_data['style']['backgroundImage'] = image_url
640
-
641
- def apply_common_styles(element_data, style):
642
- """Apply common styles that apply to all elements"""
643
- # Border properties
644
- if 'border' in style:
645
- element_data['style']['border'] = style['border']
646
- else:
647
- # Individual border properties
648
- for side in ['top', 'right', 'bottom', 'left']:
649
- border_key = f'border-{side}'
650
- if border_key in style:
651
- element_data['style'][border_key] = style[border_key]
652
-
653
- if 'border-radius' in style:
654
- element_data['style']['borderRadius'] = style['border-radius']
655
-
656
- # Opacity
657
- if 'opacity' in style:
658
- element_data['style']['opacity'] = style['opacity']
659
-
660
- # Visibility
661
- if 'visibility' in style:
662
- element_data['style']['visibility'] = style['visibility']
663
-
664
- # Box shadow
665
- if 'box-shadow' in style:
666
- element_data['style']['boxShadow'] = style['box-shadow']
667
 
668
  if __name__ == "__main__":
669
- port = int(os.environ.get("PORT", 7860))
670
- app.run(host="0.0.0.0", port=port)
 
1
+ from flask import Flask, request, jsonify
2
  import os
 
3
  import requests
 
 
 
 
 
 
 
 
 
4
 
5
  app = Flask(__name__)
6
 
7
+ # Store your Kling API key in HF "Secrets" settings
8
+ KLING_API_KEY = os.getenv("KLING_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ @app.route("/generate", methods=["POST"])
11
+ def generate():
12
+ data = request.get_json()
13
+ prompt = data.get("prompt", "")
14
 
15
  try:
16
+ response = requests.post(
17
+ "https://api.kling.ai/v1/images", # Replace with actual Kling endpoint
18
+ headers={
19
+ "Authorization": f"Bearer {KLING_API_KEY}",
20
+ "Content-Type": "application/json"
21
+ },
22
+ json={
23
+ "model": "your-model-id", # Put actual Kling model ID
24
+ "prompt": prompt,
25
+ "size": "1024x1024"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
+ )
28
+ return jsonify(response.json()), response.status_code
29
+ except Exception as e:
30
+ return jsonify({"error": str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  if __name__ == "__main__":
33
+ app.run(host="0.0.0.0", port=7860)