py-api-dumper 4.1.1__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {py_api_dumper-4.1.1/src/py_api_dumper.egg-info → py_api_dumper-4.2.0}/PKG-INFO +30 -1
  2. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/README.md +29 -0
  3. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/pyproject.toml +1 -0
  4. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper/__init__.py +102 -2
  5. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper/cli.py +9 -2
  6. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0/src/py_api_dumper.egg-info}/PKG-INFO +30 -1
  7. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/test_diff.py +10 -1
  8. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/test_dump.py +34 -1
  9. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/LICENSES/CC0-1.0.txt +0 -0
  10. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/LICENSES/MIT.txt +0 -0
  11. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/MANIFEST.in +0 -0
  12. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/setup.cfg +0 -0
  13. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper.egg-info/SOURCES.txt +0 -0
  14. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper.egg-info/dependency_links.txt +0 -0
  15. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper.egg-info/entry_points.txt +0 -0
  16. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/src/py_api_dumper.egg-info/top_level.txt +0 -0
  17. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/api_ref/__init__.py +0 -0
  18. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/api_ref/_priv_mod.py +0 -0
  19. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/api_ref/api_ref.txt +0 -0
  20. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/api_ref/ext_mod.c +0 -0
  21. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/api_ref/pub_mod.py +0 -0
  22. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/conftest.py +0 -0
  23. {py_api_dumper-4.1.1 → py_api_dumper-4.2.0}/test/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-api-dumper
3
- Version: 4.1.1
3
+ Version: 4.2.0
4
4
  Summary: Python API dumping and comparison tool
5
5
  Author-email: Karl Wette <karl.wette@anu.edu.au>
6
6
  License-Expression: MIT
@@ -77,6 +77,29 @@ $ py-api-dumper --help
77
77
  * API entries which have been *added*, i.e. present in the new API but not in
78
78
  the old API (e.g. the `b` argument in the above example).
79
79
 
80
+ * To filter out undesired entries from API dumps before comparison:
81
+ ```
82
+ $ py-api-dumper diff mymod-old.dump mymod-new.dump -i <filter>
83
+ ```
84
+ Given the following API:
85
+ ```
86
+ MODULE : mymod
87
+ CLASS : myclass
88
+ FUNCTION : __init__ : no-return-type
89
+ REQUIRED : 0 : x : int
90
+ ```
91
+ the following filters would ignore the given entries:
92
+ | `<filter>` | Ignore entries |
93
+ | ---------------------------- | ------------------------------------------ |
94
+ | `MODULE` | All modules |
95
+ | `MODULE:mymod` | All modules named `mymod` |
96
+ | `CLASS` | All classes |
97
+ | `CLASS:myclass` | All classes named `myclass` |
98
+ | `MODULE:mymod;CLASS` | All classes in modules named `mymod` |
99
+ | `MODULE:mymod;CLASS:myclass` | Class `myclass` in module `mymod` |
100
+ | `FUNCTION:__init__` | All functions named `__init__` |
101
+ | `CLASS;FUNCTION:__init__` | All functioned named `__init__` in classes |
102
+
80
103
  ## Python interface
81
104
 
82
105
  ```python
@@ -101,3 +124,9 @@ from py_api_dumper import APIDump, APIDiff
101
124
  diff.print_as_text()
102
125
  diff.save_as_json("mymod.diff")
103
126
  ```
127
+
128
+ * To filter out undesired entries from API dumps before comparison:
129
+ ```python
130
+ filter = APIFilter(("CLASS", "myclass")) # OR: APIFilter.from_str("CLASS:myclass")
131
+ diff = APIDiff.from_files("mymod-old.dump", "mymod-new.dump", filter)
132
+ ```
@@ -61,6 +61,29 @@ $ py-api-dumper --help
61
61
  * API entries which have been *added*, i.e. present in the new API but not in
62
62
  the old API (e.g. the `b` argument in the above example).
63
63
 
64
+ * To filter out undesired entries from API dumps before comparison:
65
+ ```
66
+ $ py-api-dumper diff mymod-old.dump mymod-new.dump -i <filter>
67
+ ```
68
+ Given the following API:
69
+ ```
70
+ MODULE : mymod
71
+ CLASS : myclass
72
+ FUNCTION : __init__ : no-return-type
73
+ REQUIRED : 0 : x : int
74
+ ```
75
+ the following filters would ignore the given entries:
76
+ | `<filter>` | Ignore entries |
77
+ | ---------------------------- | ------------------------------------------ |
78
+ | `MODULE` | All modules |
79
+ | `MODULE:mymod` | All modules named `mymod` |
80
+ | `CLASS` | All classes |
81
+ | `CLASS:myclass` | All classes named `myclass` |
82
+ | `MODULE:mymod;CLASS` | All classes in modules named `mymod` |
83
+ | `MODULE:mymod;CLASS:myclass` | Class `myclass` in module `mymod` |
84
+ | `FUNCTION:__init__` | All functions named `__init__` |
85
+ | `CLASS;FUNCTION:__init__` | All functioned named `__init__` in classes |
86
+
64
87
  ## Python interface
65
88
 
66
89
  ```python
@@ -85,3 +108,9 @@ from py_api_dumper import APIDump, APIDiff
85
108
  diff.print_as_text()
86
109
  diff.save_as_json("mymod.diff")
87
110
  ```
111
+
112
+ * To filter out undesired entries from API dumps before comparison:
113
+ ```python
114
+ filter = APIFilter(("CLASS", "myclass")) # OR: APIFilter.from_str("CLASS:myclass")
115
+ diff = APIDiff.from_files("mymod-old.dump", "mymod-new.dump", filter)
116
+ ```
@@ -54,6 +54,7 @@ skip-checking-raises = true
54
54
 
55
55
  [tool.pytest.ini_options]
56
56
  minversion = "6.0"
57
+ pythonpath = ["src"]
57
58
  addopts = "--cov=py_api_dumper --cov-report=term-missing --cov-fail-under=100"
58
59
  testpaths = ["test"]
59
60
 
@@ -30,9 +30,85 @@ from typing import (
30
30
  )
31
31
 
32
32
  __author__ = "Karl Wette"
33
- __version__ = "4.1.1"
33
+ __version__ = "4.2.0"
34
34
 
35
+ APIFilterType = TypeVar("APIFilterType", bound="APIFilter")
35
36
  APIDumpType = TypeVar("APIDumpType", bound="APIDump")
37
+ APIDiffType = TypeVar("APIDiffType", bound="APIDiff")
38
+
39
+
40
+ class APIFilter:
41
+ """Filter out API entries before comparing API dumps."""
42
+
43
+ def __init__(self, *patt: tuple):
44
+ """API entry filter.
45
+
46
+ Args:
47
+ *patt (tuple): Partial tuples to match to API entry.
48
+ """
49
+ self._patt = patt
50
+
51
+ @classmethod
52
+ def from_str(cls: Type[APIFilterType], filter_str: str) -> APIFilterType:
53
+ """Create an API filter from a string.
54
+
55
+ Args:
56
+ filter_str (str):
57
+ String to parse into filter.
58
+
59
+ Returns:
60
+ APIFilterType: APIFilter instance.
61
+ """
62
+
63
+ def _to_int_or_str(c):
64
+ try:
65
+ return int(c)
66
+ except ValueError:
67
+ return c
68
+
69
+ return cls(
70
+ *tuple(
71
+ tuple(_to_int_or_str(c) for c in s.split(":"))
72
+ for s in filter_str.split(";")
73
+ )
74
+ )
75
+
76
+ def match(self, entry: tuple) -> bool:
77
+ """Return if the API filter matches the given entry.
78
+
79
+ Args:
80
+ entry (tuple):
81
+ API entry to match.
82
+
83
+ Returns:
84
+ bool: True for a match, False otherwise.
85
+ """
86
+
87
+ # Match fails if pattern is longer than entry
88
+ if len(self._patt) > len(entry):
89
+ return False
90
+
91
+ # Shift filter pattern across entry
92
+ for i in range(len(entry) - len(self._patt) + 1):
93
+
94
+ # Assume found unless determined otherwise
95
+ found = True
96
+ for j in range(len(self._patt)):
97
+
98
+ # Match fails if pattern element is longer than entry element
99
+ if len(self._patt[j]) > len(entry[i + j]):
100
+ found = False
101
+ break
102
+
103
+ # Match fails if first entry elements do not match patterm
104
+ if entry[i + j][0 : len(self._patt[j])] != self._patt[j]:
105
+ found = False
106
+ break
107
+
108
+ if found:
109
+ return True
110
+
111
+ return False
36
112
 
37
113
 
38
114
  class APIDump:
@@ -408,8 +484,24 @@ class APIDump:
408
484
 
409
485
  return inst
410
486
 
487
+ def apply_ignore_filter(self, ignore_filters: List[APIFilterType]) -> "APIDump":
488
+ """Remove API entries that match filters.
411
489
 
412
- APIDiffType = TypeVar("APIDiffType", bound="APIDiff")
490
+ Args:
491
+ ignore_filters (List[APIFilterType]):
492
+ List of filters to apply.
493
+
494
+ Returns:
495
+ APIDump: filtered APIDump instance.
496
+ """
497
+
498
+ filtered_api = self._api
499
+ for f in ignore_filters:
500
+ filtered_api = set(e for e in filtered_api if not f.match(e))
501
+
502
+ return self.__class__(
503
+ dump_file=self.dump_file, modules=self.modules, api=filtered_api
504
+ )
413
505
 
414
506
 
415
507
  class APIDiff:
@@ -470,6 +562,7 @@ class APIDiff:
470
562
  cls: Type[APIDiffType],
471
563
  old_dump_file: Union[Path, str],
472
564
  new_dump_file: Union[Path, str],
565
+ ignore_filters: Optional[List[APIFilterType]] = None,
473
566
  ) -> APIDiffType:
474
567
  """Differences between two Python public API dumps loaded from files.
475
568
 
@@ -478,6 +571,8 @@ class APIDiff:
478
571
  Name of file containing dump of the old public API.
479
572
  new_dump_file (Union[Path, str]):
480
573
  Name of file containing dump of the new public API.
574
+ ignore_filters (Optional[List[APIFilterType]]):
575
+ Remove API entries that match filters.
481
576
 
482
577
  Returns:
483
578
  APIDiffType: APIDiff instance.
@@ -487,6 +582,11 @@ class APIDiff:
487
582
  old = APIDump.load_from_file(old_dump_file)
488
583
  new = APIDump.load_from_file(new_dump_file)
489
584
 
585
+ # Apply filters
586
+ if ignore_filters and len(ignore_filters) > 0:
587
+ old = old.apply_ignore_filter(ignore_filters)
588
+ new = new.apply_ignore_filter(ignore_filters)
589
+
490
590
  # Create instance
491
591
  inst = cls(old, new)
492
592
 
@@ -8,7 +8,7 @@ import argparse
8
8
  import sys
9
9
  from pathlib import Path
10
10
 
11
- from . import APIDiff, APIDump
11
+ from . import APIDiff, APIDump, APIFilter
12
12
 
13
13
 
14
14
  def _dump(args):
@@ -35,7 +35,7 @@ def _dump(args):
35
35
  def _diff(args):
36
36
 
37
37
  # Load API diff
38
- diff = APIDiff.from_files(args.old_dump, args.new_dump)
38
+ diff = APIDiff.from_files(args.old_dump, args.new_dump, args.ignore)
39
39
 
40
40
  if args.output is None:
41
41
 
@@ -84,6 +84,13 @@ def cli(*argv):
84
84
  parser_diff.add_argument(
85
85
  "-t", "--text", action="store_true", help="Output API diff in text format"
86
86
  )
87
+ parser_diff.add_argument(
88
+ "-i",
89
+ "--ignore",
90
+ action="append",
91
+ type=APIFilter.from_str,
92
+ help="Use filter to ignore API dump entries before comparison",
93
+ )
87
94
  parser_diff.add_argument(
88
95
  "old_dump", type=Path, help="File containing dump of old API"
89
96
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-api-dumper
3
- Version: 4.1.1
3
+ Version: 4.2.0
4
4
  Summary: Python API dumping and comparison tool
5
5
  Author-email: Karl Wette <karl.wette@anu.edu.au>
6
6
  License-Expression: MIT
@@ -77,6 +77,29 @@ $ py-api-dumper --help
77
77
  * API entries which have been *added*, i.e. present in the new API but not in
78
78
  the old API (e.g. the `b` argument in the above example).
79
79
 
80
+ * To filter out undesired entries from API dumps before comparison:
81
+ ```
82
+ $ py-api-dumper diff mymod-old.dump mymod-new.dump -i <filter>
83
+ ```
84
+ Given the following API:
85
+ ```
86
+ MODULE : mymod
87
+ CLASS : myclass
88
+ FUNCTION : __init__ : no-return-type
89
+ REQUIRED : 0 : x : int
90
+ ```
91
+ the following filters would ignore the given entries:
92
+ | `<filter>` | Ignore entries |
93
+ | ---------------------------- | ------------------------------------------ |
94
+ | `MODULE` | All modules |
95
+ | `MODULE:mymod` | All modules named `mymod` |
96
+ | `CLASS` | All classes |
97
+ | `CLASS:myclass` | All classes named `myclass` |
98
+ | `MODULE:mymod;CLASS` | All classes in modules named `mymod` |
99
+ | `MODULE:mymod;CLASS:myclass` | Class `myclass` in module `mymod` |
100
+ | `FUNCTION:__init__` | All functions named `__init__` |
101
+ | `CLASS;FUNCTION:__init__` | All functioned named `__init__` in classes |
102
+
80
103
  ## Python interface
81
104
 
82
105
  ```python
@@ -101,3 +124,9 @@ from py_api_dumper import APIDump, APIDiff
101
124
  diff.print_as_text()
102
125
  diff.save_as_json("mymod.diff")
103
126
  ```
127
+
128
+ * To filter out undesired entries from API dumps before comparison:
129
+ ```python
130
+ filter = APIFilter(("CLASS", "myclass")) # OR: APIFilter.from_str("CLASS:myclass")
131
+ diff = APIDiff.from_files("mymod-old.dump", "mymod-new.dump", filter)
132
+ ```
@@ -12,7 +12,7 @@ from pathlib import Path
12
12
  import api_ref
13
13
  import pytest
14
14
 
15
- from py_api_dumper import APIDiff, APIDump
15
+ from py_api_dumper import APIDiff, APIDump, APIFilter
16
16
  from py_api_dumper.cli import cli
17
17
 
18
18
 
@@ -387,3 +387,12 @@ def test_diff_cli_json(api_dump_file, api_dump_new_file, request):
387
387
  assert api_diff.added == set(
388
388
  tuple(tuple(e) for e in entry) for entry in api_diff_json["added"]
389
389
  )
390
+
391
+
392
+ def test_diff_ignore_filter(api_dump_file, api_dump_new_file, request):
393
+ """Test comparing API dumps with ignore filter."""
394
+ api_diff = APIDiff.from_files(
395
+ api_dump_file, api_dump_new_file, [APIFilter.from_str("CLASS:C1")]
396
+ )
397
+ assert len(api_diff.removed) == 4
398
+ assert len(api_diff.added) == 10
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
  import api_ref
10
10
  import pytest
11
11
 
12
- from py_api_dumper import APIDump
12
+ from py_api_dumper import APIDump, APIFilter
13
13
  from py_api_dumper.cli import cli
14
14
 
15
15
 
@@ -46,6 +46,39 @@ def test_dump_module_cli(request):
46
46
  _compare_dumps(api_dump_text)
47
47
 
48
48
 
49
+ @pytest.mark.parametrize(
50
+ "patt,n_filtered_entries",
51
+ [
52
+ [[("MEMBER", "d1")], 1],
53
+ [[("CLASS", "C1"), ("MEMBER", "f1")], 1],
54
+ [[("CLASS", "C1")], 35],
55
+ [[("MEMBER", "does-not-exist", "just-to-test", "long-filter")], 0],
56
+ ],
57
+ )
58
+ def test_dump_module_filter(request, patt, n_filtered_entries):
59
+ """Create API dump from module and apply filter."""
60
+ api_dump = APIDump.from_modules(api_ref)
61
+ n_api_entries_before = len(api_dump._api)
62
+ api_dump = api_dump.apply_ignore_filter([APIFilter(*patt)])
63
+ assert len(api_dump._api) == n_api_entries_before - n_filtered_entries
64
+
65
+
66
+ @pytest.mark.parametrize(
67
+ "filter_str,n_filtered_entries",
68
+ [
69
+ ["MEMBER:d1", 1],
70
+ ["CLASS:C1;MEMBER:f1", 1],
71
+ ["CLASS:C1", 35],
72
+ ],
73
+ )
74
+ def test_dump_module_filter_str(request, filter_str, n_filtered_entries):
75
+ """Create API dump from module and apply filter created from string."""
76
+ api_dump = APIDump.from_modules(api_ref)
77
+ n_api_entries_before = len(api_dump._api)
78
+ api_dump = api_dump.apply_ignore_filter([APIFilter.from_str(filter_str)])
79
+ assert len(api_dump._api) == n_api_entries_before - n_filtered_entries
80
+
81
+
49
82
  def test_dump_file(request):
50
83
  """Test save and loading API dumps."""
51
84
  api_dump = APIDump.from_modules(api_ref)
File without changes
File without changes