Skip to content

Commit d93f00c

Browse files
orsiniumsobolevn
andauthored
Support all environment markers (PEP-496) (#3)
* PEP-496 support * fix typing * +public function * use importlib Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
1 parent 40c9cfd commit d93f00c

File tree

2 files changed

+44
-32
lines changed

2 files changed

+44
-32
lines changed

README.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
Conditional coverage based on any rules you define!
1010

11-
Some project have different parts that relies on different environments:
11+
Some projects have different parts that relies on different environments:
1212

1313
- Python version, some code is only executed on specific versions and ignored on others
1414
- OS version, some code might be Windows, Mac, or Linux only
1515
- External packages, some code is only executed when some 3rd party package is installed
1616

1717
Current best practice is to use `# pragma: no cover` for this places in our project.
18-
This project allows to use configurable pragmas
19-
that include code to the coverage if some condition evaluates to true,
18+
This project allows to use configurable pragmas
19+
that include code to the coverage if some condition evaluates to true,
2020
and fallback to ignoring this code when condition is false.
2121

2222
Read [the announcing post](https://sobolevn.me/2020/02/conditional-coverage).
@@ -28,7 +28,7 @@ Read [the announcing post](https://sobolevn.me/2020/02/conditional-coverage).
2828
pip install coverage-conditional-plugin
2929
```
3030

31-
Then you will need to add to your `setup.cfg` or `.coveragerc` file
31+
Then you will need to add to your `setup.cfg` or `.coveragerc` file
3232
some extra configuration:
3333

3434
```ini
@@ -73,20 +73,20 @@ rules =
7373

7474
```
7575

76-
When running tests with and without `django` installed
76+
When running tests with and without `django` installed
7777
you will have `100%` coverage in both cases.
7878

79-
But, different lines will be included.
80-
With `django` installed it will include
79+
But, different lines will be included.
80+
With `django` installed it will include
8181
both `try:` and `if django is not None:` conditions.
8282

8383
When running without `django` installed,
8484
it will include `except ImportError:` line.
8585

8686

87-
## Writting pragma rules
87+
## Writing pragma rules
8888

89-
Format for pragma rules is:
89+
Format for pragma rules is:
9090

9191
```
9292
"pragma-condition": pragma-name
@@ -96,16 +96,24 @@ Code inside `"pragma-condition"` is evaluted with `eval`.
9696
Make sure that the input you pass there is trusted!
9797
`"pragma-condition"` must return `bool` value after evaluation.
9898

99-
We also provide a bunch of helpers to make writing rules easier:
99+
We support all environment markers specified in [PEP-496](https://www.python.org/dev/peps/pep-0496/).
100+
See [Strings](https://www.python.org/dev/peps/pep-0496/#strings)
101+
and [Version Numbers](https://www.python.org/dev/peps/pep-0496/#version-numbers)
102+
sections for available values. Also, we provide a bunch of additional markers:
100103

101104
- `sys_version_info` is the same as [`sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
102-
- `os_name` is the same as [`os.name`](https://docs.python.org/3/library/os.html#os.name)
103105
- `os_environ` is the same as [`os.environ`](https://docs.python.org/3/library/os.html#os.environ)
104-
- `platform_system` is the same as [`platform.system()`](https://docs.python.org/3/library/platform.html#platform.system)
105-
- `platform_release` is the same as [`platform.release()`](https://docs.python.org/3/library/platform.html#platform.release)
106106
- `is_installed` is our custom function that tries to import the passed string, returns `bool` value
107107
- `package_version` is our custom function that tries to get package version from `pkg_resources` and returns its [parsed version](https://packaging.pypa.io/en/latest/version/#packaging.version.parse)
108108

109+
Use `get_env_info` to get values for the current environment:
110+
111+
```python
112+
from coverage_conditional_plugin import get_env_info
113+
114+
get_env_info()
115+
```
116+
109117

110118
## License
111119

coverage_conditional_plugin/__init__.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
# -*- coding: utf-8 -*-
22

33
import os
4-
import platform
54
import sys
65
import traceback
7-
from typing import ClassVar, Optional, Tuple
6+
from importlib import import_module
7+
from typing import ClassVar, Dict, Optional, Tuple
88

99
import pkg_resources
1010
from coverage import CoveragePlugin
1111
from coverage.config import CoverageConfig
1212
from packaging import version
13+
from packaging.markers import default_environment
14+
15+
16+
def get_env_info() -> Dict[str, object]:
17+
"""Public helper to get the same env we pass to the plugin."""
18+
env_info: Dict[str, object] = {}
19+
env_info.update(default_environment())
20+
# Feel free to send PRs that extend this dict:
21+
env_info.update({
22+
'sys_version_info': sys.version_info,
23+
'os_environ': os.environ,
24+
'is_installed': _is_installed,
25+
'package_version': _package_version,
26+
})
27+
return env_info
1328

1429

1530
class _ConditionalCovPlugin(CoveragePlugin):
@@ -64,22 +79,12 @@ def _should_be_applied(self, code: str) -> bool:
6479
this code will be included to the coverage on 3.8+ releases.
6580
6681
"""
82+
env_info = get_env_info()
6783
try:
68-
return eval(code, { # noqa: WPS421, S307
69-
# Feel free to send PRs that extend this dict:
70-
'sys_version_info': sys.version_info,
71-
'os_name': os.name,
72-
'os_environ': os.environ,
73-
'platform_system': platform.system(),
74-
'platform_release': platform.release(),
75-
'is_installed': _is_installed,
76-
'package_version': _package_version,
77-
})
84+
return eval(code, env_info) # noqa: WPS421, S307
7885
except Exception:
79-
print( # noqa: WPS421
80-
'Exception during conditional coverage evaluation:',
81-
traceback.format_exc(),
82-
)
86+
msg = 'Exception during conditional coverage evaluation:'
87+
print(msg, traceback.format_exc()) # noqa: WPS421
8388
return False
8489

8590
def _ignore_marker(self, config: CoverageConfig, marker: str) -> None:
@@ -92,11 +97,10 @@ def _ignore_marker(self, config: CoverageConfig, marker: str) -> None:
9297
def _is_installed(package: str) -> bool:
9398
"""Helper function to detect if some package is installed."""
9499
try:
95-
__import__(package) # noqa: WPS421
100+
import_module(package)
96101
except ImportError:
97102
return False
98-
else:
99-
return True
103+
return True
100104

101105

102106
def _package_version(

0 commit comments

Comments
 (0)