Skip to content

Commit 2e34078

Browse files
committed
Add development tooling
1 parent cfd7ce5 commit 2e34078

File tree

7 files changed

+165
-35
lines changed

7 files changed

+165
-35
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,25 @@ django-sass only depends on libsass (which provides pre-built wheels for Windows
169169
and Linux), and of course Django (any version).
170170

171171

172+
Contributing
173+
------------
174+
175+
To set up a development environment, first check out this repository, create a
176+
venv, then:
177+
178+
```
179+
(myvenv)$ pip install -e ./
180+
(myvenv)$ pip install -r requirements-dev.txt
181+
```
182+
183+
Before committing, run static analysis tools:
184+
185+
```
186+
(myvenv)$ flake8
187+
(myvenv)$ mypy
188+
```
189+
190+
172191
Changelog
173192
---------
174193

ci/compare-codecov.ps1

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env pwsh
2+
3+
<#
4+
.SYNOPSIS
5+
Compares code coverage percent of local coverage.xml file to master branch
6+
(Azure Pipeline API).
7+
8+
.PARAMETER wd
9+
The working directory in which to search for current coverage.xml.
10+
11+
.PARAMETER org
12+
Name of the Azure DevOps organization where the pipeline is hosted.
13+
14+
.PARAMETER project
15+
Name of the Azure DevOps project to which the pipeline belongs.
16+
17+
.PARAMETER pipeline_name
18+
Name of the desired pipeline within the project. This is to support projects
19+
with multiple pipelines.
20+
#>
21+
22+
23+
# ---- SETUP -------------------------------------------------------------------
24+
25+
26+
param(
27+
[string] $wd = (Get-Item (Split-Path $PSCommandPath -Parent)).Parent,
28+
[string] $org = "coderedcorp",
29+
[string] $project = "coderedcms",
30+
[string] $pipeline_name = "django-sass"
31+
)
32+
33+
# Hide "UI" and progress bars.
34+
$ProgressPreference = "SilentlyContinue"
35+
36+
# API setup.
37+
$ApiBase = "https://dev.azure.com/$org/$project"
38+
39+
40+
# ---- GET CODE COVERAGE FROM RECENT BUILD -------------------------------------
41+
42+
43+
# Get list of all recent builds.
44+
$masterBuildJson = (
45+
Invoke-WebRequest "$ApiBase/_apis/build/builds?branchName=refs/heads/master&api-version=5.1"
46+
).Content | ConvertFrom-Json
47+
48+
# Get the latest matching build ID from the list of builds.
49+
foreach ($build in $masterBuildJson.value) {
50+
if ($build.definition.name -eq $pipeline_name) {
51+
$masterLatestId = $build.id
52+
break
53+
}
54+
}
55+
56+
# Retrieve code coverage for this build ID.
57+
$masterCoverageJson = (
58+
Invoke-WebRequest "$ApiBase/_apis/test/codecoverage?buildId=$masterLatestId&api-version=5.1-preview.1"
59+
).Content | ConvertFrom-Json
60+
foreach ($cov in $masterCoverageJson.coverageData.coverageStats) {
61+
if ($cov.label -eq "Lines") {
62+
$masterlinerate = [math]::Round(($cov.covered / $cov.total) * 100, 2)
63+
}
64+
}
65+
66+
67+
# ---- GET COVERAGE FROM LOCAL RUN ---------------------------------------------
68+
69+
70+
# Get current code coverage from coverage.xml file.
71+
$coveragePath = Get-ChildItem -Recurse -Filter "coverage.xml" $wd
72+
if (Test-Path -Path $coveragePath) {
73+
[xml]$BranchXML = Get-Content $coveragePath
74+
}
75+
else {
76+
Write-Host -ForegroundColor Red `
77+
"No code coverage from this build. Is pytest configured to output code coverage? Exiting."
78+
exit 1
79+
}
80+
$branchlinerate = [math]::Round([decimal]$BranchXML.coverage.'line-rate' * 100, 2)
81+
82+
83+
# ---- PRINT OUTPUT ------------------------------------------------------------
84+
85+
86+
Write-Output ""
87+
Write-Output "Master line coverage rate: $masterlinerate%"
88+
Write-Output "Branch line coverage rate: $branchlinerate%"
89+
90+
if ($masterlinerate -eq 0) {
91+
$change = "Infinite"
92+
}
93+
else {
94+
$change = [math]::Abs($branchlinerate - $masterlinerate)
95+
}
96+
97+
if ($branchlinerate -gt $masterlinerate) {
98+
Write-Host "Coverage increased by $change% 😀" -ForegroundColor Green
99+
exit 0
100+
}
101+
elseif ($branchlinerate -eq $masterlinerate) {
102+
Write-Host "Coverage has not changed." -ForegroundColor Green
103+
exit 0
104+
}
105+
else {
106+
Write-Host "Coverage decreased by $change% 😭" -ForegroundColor Red
107+
exit 4
108+
}

django_sass/management/commands/sass.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import sys
33
import time
4+
45
from django.core.management.base import BaseCommand
56
from django.contrib.staticfiles.finders import get_finders
67
import sass
@@ -50,9 +51,9 @@ def add_arguments(self, parser):
5051
help="Watch input path and re-generate css files when scss files are changed.",
5152
)
5253

53-
def compile_sass(self, outfile, **kwargs):
54+
def compile_sass(self, outfile: str, **kwargs) -> None:
5455
rval = sass.compile(**kwargs)
55-
# sass.compile() will return None of used with dirname.
56+
# sass.compile() will return None if used with dirname.
5657
# If used with filename, it will return a string of file contents.
5758
if rval and outfile:
5859
# If we got a css and sourcemap tuple, write the sourcemap.
@@ -74,7 +75,7 @@ def compile_sass(self, outfile, **kwargs):
7475
file.write(rval)
7576
file.close()
7677

77-
def handle(self, *args, **options):
78+
def handle(self, *args, **options) -> None:
7879
"""
7980
Finds all static paths used by the project, and runs sass
8081
including those paths.
@@ -102,7 +103,9 @@ def handle(self, *args, **options):
102103
if os.path.isdir(outpath):
103104
sassargs.update({"dirname": (inpath, outpath)})
104105
else:
105-
raise NotADirectoryError("Output path must also be a directory when input path is a directory.")
106+
raise NotADirectoryError(
107+
"Output path must also be a directory when input path is a directory."
108+
)
106109

107110
if os.path.isfile(inpath):
108111
sassargs.update({"filename": inpath})
@@ -115,7 +118,7 @@ def handle(self, *args, **options):
115118
if options["g"]:
116119
sassargs.update({"source_map_filename": outfile + ".map"})
117120

118-
# Watch files for changes if specified.
121+
# Watch files for changes if specified.
119122
if options["watch"]:
120123
try:
121124
self.stdout.write("Watching...")
@@ -134,7 +137,7 @@ def handle(self, *args, **options):
134137
needs_updated = True
135138
watchfiles.update({fullpath: curr_mtime})
136139

137-
# Recompile the sass if needed
140+
# Recompile the sass if needed.
138141
if needs_updated:
139142
# Catch compile errors to keep the watcher running.
140143
try:
@@ -143,14 +146,14 @@ def handle(self, *args, **options):
143146
except sass.CompileError as exc:
144147
self.stdout.write(str(exc))
145148

146-
# Go back to sleep
149+
# Go back to sleep.
147150
time.sleep(3)
148151

149152
except KeyboardInterrupt:
150153
self.stdout.write("Bye.")
151154
sys.exit(0)
152155

153-
# Write css
156+
# Write css.
154157
self.stdout.write("Writing css...")
155158
self.compile_sass(outfile, **sassargs)
156159
self.stdout.write("Done.")

requirements-dev.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
flake8
2+
mypy
3+
pytest
4+
pytest-cov
5+
pytest-django
6+
sphinx
7+
twine
8+
wheel

setup.cfg

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[flake8]
2+
max-line-length = 100
3+
exclude = migrations
4+
5+
[mypy]
6+
ignore_missing_imports = True
7+
8+
[tool:pytest]
9+
DJANGO_SETTINGS_MODULE = testproject.settings
10+
junit_family = xunit2
11+
addopts = --cov django_sass --cov-report html --cov-report xml --junitxml junit/test-results.xml
12+
python_files = tests.py test_*.py
13+
filterwarnings =
14+
ignore
15+
default:::django_sass.*

setup.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
from setuptools import setup, find_packages
3+
34
from django_sass import __version__
45

6+
57
with open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf8") as readme:
68
README = readme.read()
79

@@ -11,7 +13,8 @@
1113
author="CodeRed LLC",
1214
author_email="[email protected]",
1315
url="https://github.com/coderedcorp/django-sass",
14-
description="The absolute simplest way to use Sass with Django. Pure Python, minimal dependencies, and no special configuration required!",
16+
description=("The absolute simplest way to use Sass with Django. Pure Python, "
17+
"minimal dependencies, and no special configuration required!"),
1518
long_description=README,
1619
long_description_content_type="text/markdown",
1720
license="BSD license",
@@ -21,13 +24,6 @@
2124
"django",
2225
"libsass"
2326
],
24-
extras_require={
25-
"dev": [
26-
"pylint",
27-
"twine",
28-
"wheel"
29-
]
30-
},
3127
classifiers=[
3228
"Intended Audience :: Developers",
3329
"License :: OSI Approved :: BSD License",

testproject/testproject/settings.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,6 @@
8585
}
8686

8787

88-
# Password validation
89-
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
90-
91-
AUTH_PASSWORD_VALIDATORS = [
92-
{
93-
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
94-
},
95-
{
96-
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
97-
},
98-
{
99-
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
100-
},
101-
{
102-
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
103-
},
104-
]
105-
106-
10788
# Internationalization
10889
# https://docs.djangoproject.com/en/2.2/topics/i18n/
10990

0 commit comments

Comments
 (0)