Skip to content

Commit

Permalink
#4 Adds codecov
Browse files Browse the repository at this point in the history
  • Loading branch information
bytebutcher committed Jul 3, 2023
1 parent 344d9ba commit 3ff81af
Show file tree
Hide file tree
Showing 17 changed files with 517 additions and 56 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
__pycache__
build
dist
.coverage
4 changes: 2 additions & 2 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ python3 examples/sqlite_display_filter.py data/example.sqlite
```
```
> tables
People
Actors
```
```
> use People
> use Actors
Database changed
```
```
Expand Down
7 changes: 6 additions & 1 deletion examples/csv_display_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ def read_csv_file(csv_file):

try:
data_store = read_csv_file(csv_file)
DictDisplayFilterShell(data_store).cmdloop()
if not data_store or len(data_store) == 0:
field_names = list()
else:
# Extract field names from header.
field_names = list(data_store[0].keys())
DictDisplayFilterShell(data_store, field_names=field_names).cmdloop()
except Exception as err:
logger.error(str(err))
traceback.print_exc()
Expand Down
33 changes: 26 additions & 7 deletions examples/json_display_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import os.path
import sys
import traceback
from typing import List, Set
from typing import List, Set, Dict, Callable

from pydictdisplayfilter import DictDisplayFilter
from pydictdisplayfilter.evaluators import Evaluator
from pydictdisplayfilter.helpers import Table, DisplayFilterShell
from pydictdisplayfilter.slicers import BasicSlicer


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

def __init__(self, data_store: List[dict]):
def __init__(self,
data_store: List[dict],
field_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
""" Initializes the DictTable with a data store. """
self._data_store = data_store
super().__init__(DictDisplayFilter(data_store))
super().__init__(DictDisplayFilter(data_store, field_names, functions, slicers, evaluator))

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

def _extract_keys(self, data_store: List[dict]) -> List[str]:
""" Extracts the keys from a nested data store. """
def extract_keys(data: dict, parent_key: str='') -> Set[str]:

def extract_keys(data: dict, parent_key: str = '') -> Set[str]:
keys = set()
if isinstance(data, dict):
for k, v in data.items():
Expand All @@ -62,7 +70,7 @@ def extract_keys(data: dict, parent_key: str='') -> Set[str]:
all_keys = set()
for data in data_store:
all_keys |= extract_keys(data)
return list(all_keys)
return list(sorted(all_keys))

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

def __init__(self, data_store: List[dict]):
def __init__(self,
data_store: List[dict],
field_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
""" Initializes the DictDisplayFilterShell with a data store. """
super().__init__(JSONTable(data_store))
super().__init__(
JSONTable(
data_store=data_store,
field_names=field_names,
functions=functions,
slicers=slicers,
evaluator=evaluator))


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion examples/nmap_display_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def parse(self, nmap_xml_file) -> List[dict]:

try:
data_store = NmapXMLParser().parse(nmap_xml_file)
DictDisplayFilterShell(data_store).cmdloop()
DictDisplayFilterShell(data_store, field_names=["host", "port", "protocol", "status", "service"]).cmdloop()
except Exception as err:
logger.error(str(err))
traceback.print_exc()
Expand Down
38 changes: 33 additions & 5 deletions examples/sqlite_display_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import sqlite3
import sys
import traceback
from typing import List
from typing import List, Dict, Callable

from pydictdisplayfilter.display_filters import SQLDisplayFilter
from pydictdisplayfilter.evaluators import Evaluator
from pydictdisplayfilter.helpers import DisplayFilterShell, Table, TableError
from pydictdisplayfilter.slicers import BasicSlicer


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

def __init__(self, database_file: str):
def __init__(self,
database_file: str,
table_name: str = None,
column_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
self._database_file = database_file
self._connection = sqlite3.connect(database_file)
super().__init__(SQLDisplayFilter(self._connection))
super().__init__(
SQLDisplayFilter(
connection=self._connection,
table_name=table_name,
column_names=column_names,
functions=functions,
slicers=slicers,
evaluator=evaluator))

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

def __init__(self, database_file: str):
def __init__(self,
database_file: str,
table_name: str = None,
column_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
""" Initializes the SQLiteDisplayFilterShell with a database file. """
super().__init__(SQLiteTable(database_file=database_file))
super().__init__(
SQLiteTable(
database_file=database_file,
table_name=table_name,
column_names=column_names,
functions=functions,
slicers=slicers,
evaluator=evaluator))

def do_tables(self, *args):
""" Returns all tables which can be selected. """
Expand Down
18 changes: 9 additions & 9 deletions pydictdisplayfilter/display_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ def __init__(self,
}
self._slicer_factory = SlicerFactory(slicers)
self._evaluator = evaluator if evaluator else DefaultEvaluator()
self._functions = functions
self._field_names = field_names
self._display_filter_parser = DisplayFilterParser(field_names, functions)
self._functions = functions or []
self._field_names = field_names or []
self._display_filter_parser = DisplayFilterParser(field_names=field_names, functions=functions)

def _get_item_value(self, expression, item) -> str:
"""
Expand Down Expand Up @@ -103,7 +103,7 @@ def field_names(self) -> List[str]:
@field_names.setter
def field_names(self, field_names: List[str] = None):
self._field_names = field_names
self._display_filter_parser = DisplayFilterParser(self._field_names, self._functions)
self._display_filter_parser = DisplayFilterParser(field_names=self._field_names, functions=self._functions)

@property
def functions(self) -> Dict[str, Callable]:
Expand All @@ -112,7 +112,7 @@ def functions(self) -> Dict[str, Callable]:
@functions.setter
def functions(self, functions: Dict[str, Callable]):
self._functions = functions
self._display_filter_parser = DisplayFilterParser(self._field_names, self._functions)
self._display_filter_parser = DisplayFilterParser(field_names=self._field_names, functions=self._functions)

@abstractmethod
def filter(self, display_filter: str):
Expand All @@ -132,7 +132,7 @@ def __init__(self,
Initializes the DictDisplayFilter.
:param data: A list of dictionaries to filter on.
"""
super().__init__(field_names, functions, slicers, evaluator)
super().__init__(field_names=field_names, functions=functions, slicers=slicers, evaluator=evaluator)
self._data = data

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

def filter(self, display_filter: str):
""" Filters the data using the display filter. """
Expand Down Expand Up @@ -190,7 +190,7 @@ def __init__(self,
"""
self._connection = connection
self.table_name = table_name
super().__init__(column_names, functions, slicers, evaluator)
super().__init__(field_names=column_names, functions=functions, slicers=slicers, evaluator=evaluator)

def _validate_table_name(self, table_name: str) -> bool:
""" Checks whether the table name contains invalid characters or keywords"""
Expand Down
68 changes: 45 additions & 23 deletions pydictdisplayfilter/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,35 @@
import os
import time
import traceback
from typing import List
from collections import OrderedDict
from itertools import chain
from typing import List, Dict, Callable

from pydictdisplayfilter.display_filters import BaseDisplayFilter, DictDisplayFilter
from pydictdisplayfilter.evaluators import Evaluator
from pydictdisplayfilter.exceptions import ParserError, EvaluationError
from pydictdisplayfilter.slicers import BasicSlicer


class TableError(Exception):
pass


class TableColumnSizeCalculator:

@staticmethod
def calculate(data_store: List[dict], fields: List) -> List[int]:
""" Calculates and returns the necessary size of each column in the data store. """
field_sizes = OrderedDict()
for field in fields:
field_sizes[field] = len(field)
for item in data_store:
for key in field_sizes.keys():
if key in item:
field_sizes[key] = max(len(str(item[key])), field_sizes[key])
return list(field_sizes.values())


class Table:
""" Data store with filter capabilities and pretty table printout. """

Expand All @@ -39,22 +58,18 @@ def _make_table(self, data_store: List[dict]) -> List[str]:
""" Creates a table including header from the data store. """
if not data_store:
return []
table = [self.fields()]
column_size = self._calculate_column_size(data_store)
fields = self.fields()
table = [fields]
column_size = self._calculate_column_size(data_store, fields)
format_str = ' | '.join(["{{:<{}}}".format(i) for i in column_size])
for item in data_store:
table.append(item.values())
column_size = self._calculate_column_size(data_store)
table.append([str(item[field]) if field in item else '' for field in fields])
table.insert(1, ['-' * i for i in column_size]) # Separating line
return [""] + [ format_str.format(*item) for item in table ]

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

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

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

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

def __init__(self, data_store: List[dict]):
def __init__(self,
data_store: List[dict],
field_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
""" Initializes the DictTable with a data store. """
self._data_store = data_store
super().__init__(DictDisplayFilter(data_store))
field_names = field_names or self._extract_field_names(data_store)
super().__init__(DictDisplayFilter(data_store, field_names, functions, slicers, evaluator))

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


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

def __init__(self, data_store: List[dict]):
def __init__(self,
data_store: List[dict],
field_names: List[str] = None,
functions: Dict[str, Callable] = None,
slicers: List[BasicSlicer] = None,
evaluator: Evaluator = None):
""" Initializes the DictDisplayFilterShell with a data store. """
super().__init__(DictTable(data_store))
super().__init__(DictTable(data_store, field_names, functions, slicers, evaluator))
4 changes: 0 additions & 4 deletions pydictdisplayfilter/parsers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ def _signed_float():
)


def _number():
return pp.common.integer


def _string_list():
return pp.delimitedList(
_quoted_string()
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
'ipranger==1.1.2',
'python-dateutil==2.8.2',
'pytest==7.3.1',
'pytest-cov==4.0.0'
'pytest-cov==4.0.0',
'pexpect==4.8.0'
],
include_package_data=True,
)
Loading

0 comments on commit 3ff81af

Please sign in to comment.