Skip to content

Commit 3ff81af

Browse files
committed
#4 Adds codecov
1 parent 344d9ba commit 3ff81af

17 files changed

+517
-56
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
__pycache__
44
build
55
dist
6+
.coverage

docs/EXAMPLES.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ python3 examples/sqlite_display_filter.py data/example.sqlite
115115
```
116116
```
117117
> tables
118-
People
118+
Actors
119119
```
120120
```
121-
> use People
121+
> use Actors
122122
Database changed
123123
```
124124
```

examples/csv_display_filter.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ def read_csv_file(csv_file):
5151

5252
try:
5353
data_store = read_csv_file(csv_file)
54-
DictDisplayFilterShell(data_store).cmdloop()
54+
if not data_store or len(data_store) == 0:
55+
field_names = list()
56+
else:
57+
# Extract field names from header.
58+
field_names = list(data_store[0].keys())
59+
DictDisplayFilterShell(data_store, field_names=field_names).cmdloop()
5560
except Exception as err:
5661
logger.error(str(err))
5762
traceback.print_exc()

examples/json_display_filter.py

+26-7
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import os.path
2121
import sys
2222
import traceback
23-
from typing import List, Set
23+
from typing import List, Set, Dict, Callable
2424

2525
from pydictdisplayfilter import DictDisplayFilter
26+
from pydictdisplayfilter.evaluators import Evaluator
2627
from pydictdisplayfilter.helpers import Table, DisplayFilterShell
28+
from pydictdisplayfilter.slicers import BasicSlicer
2729

2830

2931
def read_json_file(json_file):
@@ -38,18 +40,24 @@ def read_json_file(json_file):
3840
class JSONTable(Table):
3941
""" Data store with filter capabilities and pretty table printout. """
4042

41-
def __init__(self, data_store: List[dict]):
43+
def __init__(self,
44+
data_store: List[dict],
45+
field_names: List[str] = None,
46+
functions: Dict[str, Callable] = None,
47+
slicers: List[BasicSlicer] = None,
48+
evaluator: Evaluator = None):
4249
""" Initializes the DictTable with a data store. """
4350
self._data_store = data_store
44-
super().__init__(DictDisplayFilter(data_store))
51+
super().__init__(DictDisplayFilter(data_store, field_names, functions, slicers, evaluator))
4552

4653
def _make_table(self, data_store: List[dict]) -> List[str]:
4754
""" Creates a view for nested data items. """
4855
return [json.dumps(data) for data in data_store]
4956

5057
def _extract_keys(self, data_store: List[dict]) -> List[str]:
5158
""" Extracts the keys from a nested data store. """
52-
def extract_keys(data: dict, parent_key: str='') -> Set[str]:
59+
60+
def extract_keys(data: dict, parent_key: str = '') -> Set[str]:
5361
keys = set()
5462
if isinstance(data, dict):
5563
for k, v in data.items():
@@ -62,7 +70,7 @@ def extract_keys(data: dict, parent_key: str='') -> Set[str]:
6270
all_keys = set()
6371
for data in data_store:
6472
all_keys |= extract_keys(data)
65-
return list(all_keys)
73+
return list(sorted(all_keys))
6674

6775
def fields(self, thorough: bool = True) -> List[str]:
6876
""" Returns the field names used in the data store. """
@@ -75,9 +83,20 @@ def fields(self, thorough: bool = True) -> List[str]:
7583
class JSONDisplayFilterShell(DisplayFilterShell):
7684
""" A little shell for querying a list of dictionaries using the display filter. """
7785

78-
def __init__(self, data_store: List[dict]):
86+
def __init__(self,
87+
data_store: List[dict],
88+
field_names: List[str] = None,
89+
functions: Dict[str, Callable] = None,
90+
slicers: List[BasicSlicer] = None,
91+
evaluator: Evaluator = None):
7992
""" Initializes the DictDisplayFilterShell with a data store. """
80-
super().__init__(JSONTable(data_store))
93+
super().__init__(
94+
JSONTable(
95+
data_store=data_store,
96+
field_names=field_names,
97+
functions=functions,
98+
slicers=slicers,
99+
evaluator=evaluator))
81100

82101

83102
if __name__ == '__main__':

examples/nmap_display_filter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def parse(self, nmap_xml_file) -> List[dict]:
8989

9090
try:
9191
data_store = NmapXMLParser().parse(nmap_xml_file)
92-
DictDisplayFilterShell(data_store).cmdloop()
92+
DictDisplayFilterShell(data_store, field_names=["host", "port", "protocol", "status", "service"]).cmdloop()
9393
except Exception as err:
9494
logger.error(str(err))
9595
traceback.print_exc()

examples/sqlite_display_filter.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import sqlite3
2121
import sys
2222
import traceback
23-
from typing import List
23+
from typing import List, Dict, Callable
2424

2525
from pydictdisplayfilter.display_filters import SQLDisplayFilter
26+
from pydictdisplayfilter.evaluators import Evaluator
2627
from pydictdisplayfilter.helpers import DisplayFilterShell, Table, TableError
28+
from pydictdisplayfilter.slicers import BasicSlicer
2729

2830

2931
class TableNotFoundError(TableError):
@@ -41,10 +43,23 @@ def __init__(self):
4143
class SQLiteTable(Table):
4244
""" Data store with filter capabilities and pretty table printout. """
4345

44-
def __init__(self, database_file: str):
46+
def __init__(self,
47+
database_file: str,
48+
table_name: str = None,
49+
column_names: List[str] = None,
50+
functions: Dict[str, Callable] = None,
51+
slicers: List[BasicSlicer] = None,
52+
evaluator: Evaluator = None):
4553
self._database_file = database_file
4654
self._connection = sqlite3.connect(database_file)
47-
super().__init__(SQLDisplayFilter(self._connection))
55+
super().__init__(
56+
SQLDisplayFilter(
57+
connection=self._connection,
58+
table_name=table_name,
59+
column_names=column_names,
60+
functions=functions,
61+
slicers=slicers,
62+
evaluator=evaluator))
4863

4964
def _table_exists(self, table_name: str) -> bool:
5065
""" Checks whether the specified table does exist. """
@@ -101,9 +116,22 @@ def filter(self, display_filter: str) -> str:
101116
class SQLiteDisplayFilterShell(DisplayFilterShell):
102117
""" A little shell for querying a SQLite database using the display filter. """
103118

104-
def __init__(self, database_file: str):
119+
def __init__(self,
120+
database_file: str,
121+
table_name: str = None,
122+
column_names: List[str] = None,
123+
functions: Dict[str, Callable] = None,
124+
slicers: List[BasicSlicer] = None,
125+
evaluator: Evaluator = None):
105126
""" Initializes the SQLiteDisplayFilterShell with a database file. """
106-
super().__init__(SQLiteTable(database_file=database_file))
127+
super().__init__(
128+
SQLiteTable(
129+
database_file=database_file,
130+
table_name=table_name,
131+
column_names=column_names,
132+
functions=functions,
133+
slicers=slicers,
134+
evaluator=evaluator))
107135

108136
def do_tables(self, *args):
109137
""" Returns all tables which can be selected. """

pydictdisplayfilter/display_filters.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def __init__(self,
5252
}
5353
self._slicer_factory = SlicerFactory(slicers)
5454
self._evaluator = evaluator if evaluator else DefaultEvaluator()
55-
self._functions = functions
56-
self._field_names = field_names
57-
self._display_filter_parser = DisplayFilterParser(field_names, functions)
55+
self._functions = functions or []
56+
self._field_names = field_names or []
57+
self._display_filter_parser = DisplayFilterParser(field_names=field_names, functions=functions)
5858

5959
def _get_item_value(self, expression, item) -> str:
6060
"""
@@ -103,7 +103,7 @@ def field_names(self) -> List[str]:
103103
@field_names.setter
104104
def field_names(self, field_names: List[str] = None):
105105
self._field_names = field_names
106-
self._display_filter_parser = DisplayFilterParser(self._field_names, self._functions)
106+
self._display_filter_parser = DisplayFilterParser(field_names=self._field_names, functions=self._functions)
107107

108108
@property
109109
def functions(self) -> Dict[str, Callable]:
@@ -112,7 +112,7 @@ def functions(self) -> Dict[str, Callable]:
112112
@functions.setter
113113
def functions(self, functions: Dict[str, Callable]):
114114
self._functions = functions
115-
self._display_filter_parser = DisplayFilterParser(self._field_names, self._functions)
115+
self._display_filter_parser = DisplayFilterParser(field_names=self._field_names, functions=self._functions)
116116

117117
@abstractmethod
118118
def filter(self, display_filter: str):
@@ -132,7 +132,7 @@ def __init__(self,
132132
Initializes the DictDisplayFilter.
133133
:param data: A list of dictionaries to filter on.
134134
"""
135-
super().__init__(field_names, functions, slicers, evaluator)
135+
super().__init__(field_names=field_names, functions=functions, slicers=slicers, evaluator=evaluator)
136136
self._data = data
137137

138138
def filter(self, display_filter: str):
@@ -154,9 +154,9 @@ def __init__(self,
154154
Initializes the ListDisplayFilter.
155155
:param data: A list of lists to filter on.
156156
"""
157-
super().__init__([
157+
super().__init__(data=[
158158
dict(zip(field_names, item)) for item in data
159-
], field_names, functions, slicers, evaluator)
159+
], field_names=field_names, functions=functions, slicers=slicers, evaluator=evaluator)
160160

161161
def filter(self, display_filter: str):
162162
""" Filters the data using the display filter. """
@@ -190,7 +190,7 @@ def __init__(self,
190190
"""
191191
self._connection = connection
192192
self.table_name = table_name
193-
super().__init__(column_names, functions, slicers, evaluator)
193+
super().__init__(field_names=column_names, functions=functions, slicers=slicers, evaluator=evaluator)
194194

195195
def _validate_table_name(self, table_name: str) -> bool:
196196
""" Checks whether the table name contains invalid characters or keywords"""

pydictdisplayfilter/helpers.py

+45-23
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,35 @@
1919
import os
2020
import time
2121
import traceback
22-
from typing import List
22+
from collections import OrderedDict
23+
from itertools import chain
24+
from typing import List, Dict, Callable
2325

2426
from pydictdisplayfilter.display_filters import BaseDisplayFilter, DictDisplayFilter
27+
from pydictdisplayfilter.evaluators import Evaluator
2528
from pydictdisplayfilter.exceptions import ParserError, EvaluationError
29+
from pydictdisplayfilter.slicers import BasicSlicer
2630

2731

2832
class TableError(Exception):
2933
pass
3034

3135

36+
class TableColumnSizeCalculator:
37+
38+
@staticmethod
39+
def calculate(data_store: List[dict], fields: List) -> List[int]:
40+
""" Calculates and returns the necessary size of each column in the data store. """
41+
field_sizes = OrderedDict()
42+
for field in fields:
43+
field_sizes[field] = len(field)
44+
for item in data_store:
45+
for key in field_sizes.keys():
46+
if key in item:
47+
field_sizes[key] = max(len(str(item[key])), field_sizes[key])
48+
return list(field_sizes.values())
49+
50+
3251
class Table:
3352
""" Data store with filter capabilities and pretty table printout. """
3453

@@ -39,22 +58,18 @@ def _make_table(self, data_store: List[dict]) -> List[str]:
3958
""" Creates a table including header from the data store. """
4059
if not data_store:
4160
return []
42-
table = [self.fields()]
43-
column_size = self._calculate_column_size(data_store)
61+
fields = self.fields()
62+
table = [fields]
63+
column_size = self._calculate_column_size(data_store, fields)
4464
format_str = ' | '.join(["{{:<{}}}".format(i) for i in column_size])
4565
for item in data_store:
46-
table.append(item.values())
47-
column_size = self._calculate_column_size(data_store)
66+
table.append([str(item[field]) if field in item else '' for field in fields])
4867
table.insert(1, ['-' * i for i in column_size]) # Separating line
4968
return [""] + [ format_str.format(*item) for item in table ]
5069

51-
def _calculate_column_size(self, data_store: List[dict]) -> List[int]:
70+
def _calculate_column_size(self, data_store: List[dict], fields: List) -> List[int]:
5271
""" Calculates and returns the necessary size of each column in the data store. """
53-
header = list(data_store[0].keys())
54-
items = [list(item.values()) for item in data_store]
55-
return [
56-
max(len(str(item)) for item in column) for column in zip(*items + [header])
57-
]
72+
return TableColumnSizeCalculator.calculate(data_store, fields)
5873

5974
def _make_footer(self, items: List[dict], duration: float) -> List[str]:
6075
""" Creates a footer for the table which prints some statistics. """
@@ -70,7 +85,7 @@ def _make_footer(self, items: List[dict], duration: float) -> List[str]:
7085

7186
def fields(self) -> List[str]:
7287
""" Returns the field names used in the data store. """
73-
raise NotImplementedError()
88+
return self._display_filter.field_names
7489

7590
def filter(self, display_filter: str) -> str:
7691
"""
@@ -177,22 +192,29 @@ def do_exit(self, *args):
177192
class DictTable(Table):
178193
""" Data store with filter capabilities and pretty table printout. """
179194

180-
def __init__(self, data_store: List[dict]):
195+
def __init__(self,
196+
data_store: List[dict],
197+
field_names: List[str] = None,
198+
functions: Dict[str, Callable] = None,
199+
slicers: List[BasicSlicer] = None,
200+
evaluator: Evaluator = None):
181201
""" Initializes the DictTable with a data store. """
182-
self._data_store = data_store
183-
super().__init__(DictDisplayFilter(data_store))
202+
field_names = field_names or self._extract_field_names(data_store)
203+
super().__init__(DictDisplayFilter(data_store, field_names, functions, slicers, evaluator))
184204

185-
def fields(self) -> List[str]:
186-
""" Returns the field names used in the data store. """
187-
if not self._data_store:
188-
# No items in data store, so there are no fields to query either.
189-
return list()
190-
return list(self._data_store[0].keys())
205+
def _extract_field_names(self, data_store: List[dict]) -> List[str]:
206+
""" Extracts the field names from the given data store. """
207+
return sorted(set(chain.from_iterable(row.keys() for row in data_store)))
191208

192209

193210
class DictDisplayFilterShell(DisplayFilterShell):
194211
""" A little shell for querying a list of dictionaries using the display filter. """
195212

196-
def __init__(self, data_store: List[dict]):
213+
def __init__(self,
214+
data_store: List[dict],
215+
field_names: List[str] = None,
216+
functions: Dict[str, Callable] = None,
217+
slicers: List[BasicSlicer] = None,
218+
evaluator: Evaluator = None):
197219
""" Initializes the DictDisplayFilterShell with a data store. """
198-
super().__init__(DictTable(data_store))
220+
super().__init__(DictTable(data_store, field_names, functions, slicers, evaluator))

pydictdisplayfilter/parsers/common.py

-4
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,6 @@ def _signed_float():
4141
)
4242

4343

44-
def _number():
45-
return pp.common.integer
46-
47-
4844
def _string_list():
4945
return pp.delimitedList(
5046
_quoted_string()

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
'ipranger==1.1.2',
3232
'python-dateutil==2.8.2',
3333
'pytest==7.3.1',
34-
'pytest-cov==4.0.0'
34+
'pytest-cov==4.0.0',
35+
'pexpect==4.8.0'
3536
],
3637
include_package_data=True,
3738
)

0 commit comments

Comments
 (0)