Skip to content

Commit eb60c75

Browse files
committed
*v0.1
1 parent 90c4061 commit eb60c75

File tree

5 files changed

+256
-0
lines changed

5 files changed

+256
-0
lines changed

.gitignore

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Created by https://www.gitignore.io/api/python
2+
# Edit at https://www.gitignore.io/?templates=python
3+
4+
### Python ###
5+
# Byte-compiled / optimized / DLL files
6+
__pycache__/
7+
*.py[cod]
8+
*$py.class
9+
10+
# C extensions
11+
*.so
12+
13+
# Distribution / packaging
14+
.Python
15+
build/
16+
develop-eggs/
17+
dist/
18+
downloads/
19+
eggs/
20+
.eggs/
21+
lib/
22+
lib64/
23+
parts/
24+
sdist/
25+
var/
26+
wheels/
27+
share/python-wheels/
28+
*.egg-info/
29+
.installed.cfg
30+
*.egg
31+
MANIFEST
32+
33+
# PyInstaller
34+
# Usually these files are written by a python script from a template
35+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
36+
*.manifest
37+
*.spec
38+
39+
# Installer logs
40+
pip-log.txt
41+
pip-delete-this-directory.txt
42+
43+
# Unit test / coverage reports
44+
htmlcov/
45+
.tox/
46+
.nox/
47+
.coverage
48+
.coverage.*
49+
.cache
50+
nosetests.xml
51+
coverage.xml
52+
*.cover
53+
.hypothesis/
54+
.pytest_cache/
55+
56+
# Translations
57+
*.mo
58+
*.pot
59+
60+
# Django stuff:
61+
*.log
62+
local_settings.py
63+
db.sqlite3
64+
65+
# Flask stuff:
66+
instance/
67+
.webassets-cache
68+
69+
# Scrapy stuff:
70+
.scrapy
71+
72+
# Sphinx documentation
73+
docs/_build/
74+
75+
# PyBuilder
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
.python-version
87+
88+
# celery beat schedule file
89+
celerybeat-schedule
90+
91+
# SageMath parsed files
92+
*.sage.py
93+
94+
# Environments
95+
.env
96+
.venv
97+
env/
98+
venv/
99+
ENV/
100+
env.bak/
101+
venv.bak/
102+
103+
# Spyder project settings
104+
.spyderproject
105+
.spyproject
106+
107+
# Rope project settings
108+
.ropeproject
109+
110+
# mkdocs documentation
111+
/site
112+
113+
# mypy
114+
.mypy_cache/
115+
.dmypy.json
116+
dmypy.json
117+
118+
# Pyre type checker
119+
.pyre/
120+
121+
### Python Patch ###
122+
.venv/
123+
124+
# End of https://www.gitignore.io/api/python

lighthouse/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .runner import LighthouseRunner

lighthouse/report.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from datetime import timedelta
2+
from collections import namedtuple
3+
4+
AuditResult = namedtuple('AuditResult', ['title', 'score', 'display'])
5+
AuditCategoryResult = namedtuple('AuditCategoryResult', ['passed', 'failed'])
6+
7+
BASE_TIMINGS = [
8+
'first-contentful-paint',
9+
'speed-index',
10+
'interactive',
11+
'first-meaningful-paint',
12+
'first-cpu-idle',
13+
'estimated-input-latency',
14+
'time-to-first-byte',
15+
]
16+
17+
18+
class LighthouseReport(object):
19+
def __init__(self, data, timings=BASE_TIMINGS):
20+
self.__data = data
21+
self.__timings = timings
22+
23+
@property
24+
def score(self):
25+
return {
26+
k: v.get('score', '0')
27+
for k, v
28+
in self.__data['categories'].items()
29+
}
30+
31+
@property
32+
def timings(self):
33+
return {
34+
k: timedelta(milliseconds=v.get('rawValue'))
35+
for k, v
36+
in self.__data['audits'].items()
37+
if k in self.__timings
38+
}
39+
40+
@property
41+
def audits(self):
42+
res = {}
43+
44+
for category, data in self.__data['categories'].items():
45+
all_audit_refs = [
46+
x.get('id')
47+
for x in data['auditRefs']
48+
]
49+
all_audits = {k: self.__data['audits'][k] for k in all_audit_refs}
50+
sdm_to_reject = ['manual', 'notApplicable', 'informative']
51+
passed_audits = [
52+
AuditResult(**{
53+
'title': v['title'],
54+
'score': v['score'],
55+
'display': v.get('displayValue'),
56+
})
57+
for k, v in all_audits.items()
58+
if v.get('score', 0) == 1 and
59+
v.get('scoreDisplayMode') not in sdm_to_reject
60+
]
61+
62+
failed_audits = [
63+
AuditResult(**{
64+
'title': v['title'],
65+
'score': v['score'],
66+
'display': v.get('displayValue'),
67+
})
68+
for k, v in all_audits.items()
69+
if v.get('score', 0) < 1 and
70+
v.get('scoreDisplayMode') not in sdm_to_reject
71+
]
72+
73+
res[category] = AuditCategoryResult(passed_audits, failed_audits)
74+
return res

lighthouse/runner.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Python Standard Library
2+
import json
3+
import os
4+
import subprocess
5+
import tempfile
6+
7+
# Own
8+
from report import LighthouseReport
9+
10+
11+
class LighthouseRunner(object):
12+
def __init__(self, url, form_factor='mobile', quiet=True):
13+
_, self.__report_path = tempfile.mkstemp(suffix='.json')
14+
self._run(url, form_factor, quiet)
15+
self.report = self._get_report()
16+
self._clean()
17+
18+
def _run(self, url, form_factor, quiet):
19+
subprocess.call([
20+
'lighthouse',
21+
url,
22+
'--quiet' if quiet else '',
23+
'--chrome-flags',
24+
'"--headless"',
25+
'--preset',
26+
'full',
27+
'--emulated-form-factor',
28+
form_factor,
29+
'--output',
30+
'json',
31+
'--output-path',
32+
'{0}'.format(self.__report_path),
33+
])
34+
35+
def _get_report(self):
36+
try:
37+
with open(self.__report_path, 'r') as fil:
38+
return LighthouseReport(json.load(fil))
39+
except ValueError:
40+
pass # @TODO
41+
42+
def _clean(self):
43+
os.remove(self.__report_path)

setup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
3+
from setuptools import setup, find_packages
4+
5+
setup(
6+
name='lighthouse',
7+
version='1.0',
8+
description='Lightouse runner',
9+
author='Adam Cupiał',
10+
author_email='cupial.adam@gmail.com',
11+
url='n/a',
12+
packages=find_packages(),
13+
install_requires=[]
14+
)

0 commit comments

Comments
 (0)