diff --git a/poetry.lock b/poetry.lock
index 3668c43..e9f3aaa 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -52,6 +52,14 @@ category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+[[package]]
+name = "cssselect"
+version = "1.2.0"
+description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
[[package]]
name = "exceptiongroup"
version = "1.0.1"
@@ -84,6 +92,20 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "lxml"
+version = "4.9.1"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=0.29.7)"]
+
[[package]]
name = "mccabe"
version = "0.7.0"
@@ -201,7 +223,7 @@ python-versions = ">=3.7"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
-content-hash = "ee2f5019e2f02958756ac6b12437952f505847f6f9de0d7f673116ed428b12a4"
+content-hash = "765977e700b56e9b852f6ca6f5d54e2c1343b3a07b9220e83ef969a277f67866"
[metadata.files]
attrs = [
@@ -239,6 +261,10 @@ colorama = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+cssselect = [
+ {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"},
+ {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"},
+]
exceptiongroup = [
{file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"},
{file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"},
@@ -251,6 +277,78 @@ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
+lxml = [
+ {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"},
+ {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"},
+ {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
+ {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"},
+ {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"},
+ {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"},
+ {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"},
+ {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"},
+ {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"},
+ {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"},
+ {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"},
+ {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"},
+ {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"},
+ {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"},
+ {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"},
+ {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"},
+ {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"},
+ {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"},
+ {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"},
+ {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"},
+ {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"},
+ {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"},
+ {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"},
+ {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"},
+ {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"},
+ {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"},
+ {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"},
+ {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"},
+ {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"},
+ {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"},
+ {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"},
+ {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"},
+ {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"},
+ {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"},
+ {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"},
+ {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"},
+ {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"},
+ {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"},
+ {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"},
+ {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"},
+ {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
+]
mccabe = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
diff --git a/pyproject.toml b/pyproject.toml
index f938922..ce52960 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,8 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
+lxml = "^4.9.1"
+cssselect = "^1.2.0"
[tool.poetry.group.dev.dependencies]
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index f029845..0000000
--- a/tests/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import unittest
-from saucebrush.tests.filters import FilterTestCase
-from saucebrush.tests.sources import SourceTestCase
-from saucebrush.tests.emitters import EmitterTestCase
-from saucebrush.tests.recipes import RecipeTestCase
-from saucebrush.tests.stats import StatsTestCase
-
-filter_suite = unittest.TestLoader().loadTestsFromTestCase(FilterTestCase)
-source_suite = unittest.TestLoader().loadTestsFromTestCase(SourceTestCase)
-emitter_suite = unittest.TestLoader().loadTestsFromTestCase(EmitterTestCase)
-recipe_suite = unittest.TestLoader().loadTestsFromTestCase(RecipeTestCase)
-stats_suite = unittest.TestLoader().loadTestsFromTestCase(StatsTestCase)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/emitters.py b/tests/emitters.py
deleted file mode 100644
index 1a6a22b..0000000
--- a/tests/emitters.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from contextlib import closing
-from io import StringIO
-import os
-import unittest
-
-from saucebrush.emitters import (
- DebugEmitter,
- CSVEmitter,
- CountEmitter,
- SqliteEmitter,
- SqlDumpEmitter,
-)
-
-
-class EmitterTestCase(unittest.TestCase):
- def test_debug_emitter(self):
- with closing(StringIO()) as output:
- de = DebugEmitter(output)
- list(de.attach([1, 2, 3]))
- self.assertEqual(output.getvalue(), "1\n2\n3\n")
-
- def test_count_emitter(self):
-
- # values for test
- values = [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- 17,
- 18,
- 19,
- 20,
- 21,
- 22,
- ]
-
- with closing(StringIO()) as output:
-
- # test without of parameter
- ce = CountEmitter(every=10, outfile=output, format="%(count)s records\n")
- list(ce.attach(values))
- self.assertEqual(output.getvalue(), "10 records\n20 records\n")
- ce.done()
- self.assertEqual(output.getvalue(), "10 records\n20 records\n22 records\n")
-
- with closing(StringIO()) as output:
-
- # test with of parameter
- ce = CountEmitter(every=10, outfile=output, of=len(values))
- list(ce.attach(values))
- self.assertEqual(output.getvalue(), "10 of 22\n20 of 22\n")
- ce.done()
- self.assertEqual(output.getvalue(), "10 of 22\n20 of 22\n22 of 22\n")
-
- def test_csv_emitter(self):
-
- try:
- import cStringIO # if Python 2.x then use old cStringIO
-
- io = cStringIO.StringIO()
- except Exception:
- io = StringIO() # if Python 3.x then use StringIO
-
- with closing(io) as output:
- ce = CSVEmitter(output, ("x", "y", "z"))
- list(ce.attach([{"x": 1, "y": 2, "z": 3}, {"x": 5, "y": 5, "z": 5}]))
- self.assertEqual(output.getvalue(), "x,y,z\r\n1,2,3\r\n5,5,5\r\n")
-
- def test_sqlite_emitter(self):
-
- import sqlite3
- import tempfile
-
- with closing(tempfile.NamedTemporaryFile(suffix=".db")) as f:
- db_path = f.name
-
- sle = SqliteEmitter(db_path, "testtable", fieldnames=("a", "b", "c"))
- list(sle.attach([{"a": "1", "b": "2", "c": "3"}]))
- sle.done()
-
- with closing(sqlite3.connect(db_path)) as conn:
- cur = conn.cursor()
- cur.execute("""SELECT a, b, c FROM testtable""")
- results = cur.fetchall()
-
- os.unlink(db_path)
-
- self.assertEqual(results, [("1", "2", "3")])
-
- def test_sql_dump_emitter(self):
-
- with closing(StringIO()) as bffr:
-
- sde = SqlDumpEmitter(bffr, "testtable", ("a", "b"))
- list(sde.attach([{"a": 1, "b": "2"}]))
- sde.done()
-
- self.assertEqual(
- bffr.getvalue(), "INSERT INTO `testtable` (`a`,`b`) VALUES (1,'2');\n"
- )
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/recipes.py b/tests/recipes.py
deleted file mode 100644
index dae3668..0000000
--- a/tests/recipes.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import unittest
-from saucebrush import Recipe, run_recipe, SaucebrushError, OvercookedError
-from saucebrush.filters import Filter
-
-
-class Raiser(Filter):
- def process_record(self, record):
- raise Exception("bad record")
-
-
-class Saver(Filter):
- def __init__(self):
- self.saved = []
-
- def process_record(self, record):
- self.saved.append(record)
- return record
-
-
-class RecipeTestCase(unittest.TestCase):
- def test_error_stream(self):
- saver = Saver()
- recipe = Recipe(Raiser(), error_stream=saver)
- recipe.run([{"a": 1}, {"b": 2}])
- recipe.done()
-
- self.assertEqual(saver.saved[0]["record"], {"a": 1})
- self.assertEqual(saver.saved[1]["record"], {"b": 2})
-
- # Must pass either a Recipe, a Filter or an iterable of Filters
- # as the error_stream argument
- self.assertRaises(SaucebrushError, Recipe, error_stream=5)
-
- def test_run_recipe(self):
- saver = Saver()
- run_recipe([1, 2], saver)
-
- self.assertEqual(saver.saved, [1, 2])
-
- def test_done(self):
- saver = Saver()
- recipe = Recipe(saver)
- recipe.run([1])
- recipe.done()
-
- self.assertRaises(OvercookedError, recipe.run, [2])
- self.assertRaises(OvercookedError, recipe.done)
- self.assertEqual(saver.saved, [1])
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/sources.py b/tests/sources.py
deleted file mode 100644
index 189cb9d..0000000
--- a/tests/sources.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from io import StringIO
-import unittest
-
-from saucebrush.sources import (
- CSVSource,
- FixedWidthFileSource,
- HtmlTableSource,
- JSONSource,
-)
-
-
-class SourceTestCase(unittest.TestCase):
- def _get_csv(self):
- data = """a,b,c
-1,2,3
-5,5,5
-1,10,100"""
- return StringIO(data)
-
- def test_csv_source_basic(self):
- source = CSVSource(self._get_csv())
- expected_data = [
- {"a": "1", "b": "2", "c": "3"},
- {"a": "5", "b": "5", "c": "5"},
- {"a": "1", "b": "10", "c": "100"},
- ]
- self.assertEqual(list(source), expected_data)
-
- def test_csv_source_fieldnames(self):
- source = CSVSource(self._get_csv(), ["x", "y", "z"])
- expected_data = [
- {"x": "a", "y": "b", "z": "c"},
- {"x": "1", "y": "2", "z": "3"},
- {"x": "5", "y": "5", "z": "5"},
- {"x": "1", "y": "10", "z": "100"},
- ]
- self.assertEqual(list(source), expected_data)
-
- def test_csv_source_skiprows(self):
- source = CSVSource(self._get_csv(), skiprows=1)
- expected_data = [
- {"a": "5", "b": "5", "c": "5"},
- {"a": "1", "b": "10", "c": "100"},
- ]
- self.assertEqual(list(source), expected_data)
-
- def test_fixed_width_source(self):
- data = StringIO("JamesNovember 3 1986\nTim September151999")
- fields = (("name", 5), ("month", 9), ("day", 2), ("year", 4))
- source = FixedWidthFileSource(data, fields)
- expected_data = [
- {"name": "James", "month": "November", "day": "3", "year": "1986"},
- {"name": "Tim", "month": "September", "day": "15", "year": "1999"},
- ]
- self.assertEqual(list(source), expected_data)
-
- def test_json_source(self):
-
- content = StringIO("""[{"a": 1, "b": "2", "c": 3}]""")
-
- js = JSONSource(content)
- self.assertEqual(list(js), [{"a": 1, "b": "2", "c": 3}])
-
- def test_html_table_source(self):
-
- content = StringIO(
- """
-
-
-
- a |
- b |
- c |
-
-
- 1 |
- 2 |
- 3 |
-
-
-
- """
- )
-
- try:
-
- hts = HtmlTableSource(content, "thetable")
- self.assertEqual(list(hts), [{"a": "1", "b": "2", "c": "3"}])
-
- except ImportError:
- # Python 2.6 doesn't have skipTest. We'll just suffer without it.
- if hasattr(self, "skipTest"):
- self.skipTest("lxml is not installed")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/stats.py b/tests/stats.py
deleted file mode 100644
index 757c256..0000000
--- a/tests/stats.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import unittest
-from saucebrush.stats import Sum, Average, Median, MinMax, StandardDeviation, Histogram
-
-
-class StatsTestCase(unittest.TestCase):
- def _simple_data(self):
- return [
- {"a": 1, "b": 2, "c": 3},
- {"a": 5, "b": 5, "c": 5},
- {"a": 1, "b": 10, "c": 100},
- ]
-
- def test_sum(self):
- fltr = Sum("b")
- list(fltr.attach(self._simple_data()))
- self.assertEqual(fltr.value(), 17)
-
- def test_average(self):
- fltr = Average("c")
- list(fltr.attach(self._simple_data()))
- self.assertEqual(fltr.value(), 36.0)
-
- def test_median(self):
- # odd number of values
- fltr = Median("a")
- list(fltr.attach(self._simple_data()))
- self.assertEqual(fltr.value(), 1)
-
- # even number of values
- fltr = Median("a")
- list(fltr.attach(self._simple_data()[:2]))
- self.assertEqual(fltr.value(), 3)
-
- def test_minmax(self):
- fltr = MinMax("b")
- list(fltr.attach(self._simple_data()))
- self.assertEqual(fltr.value(), (2, 10))
-
- def test_standard_deviation(self):
- fltr = StandardDeviation("c")
- list(fltr.attach(self._simple_data()))
- self.assertEqual(fltr.average(), 36.0)
- self.assertEqual(fltr.median(), 5)
- self.assertEqual(fltr.value(), (55.4346462061408, 3073.0))
- self.assertEqual(fltr.value(True), (45.2621990922521, 2048.6666666666665))
-
- def test_histogram(self):
- fltr = Histogram("a")
- fltr.label_length = 1
- list(fltr.attach(self._simple_data()))
- self.assertEqual(str(fltr), "\n1 **\n5 *\n")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_emitters.py b/tests/test_emitters.py
new file mode 100644
index 0000000..fead1e3
--- /dev/null
+++ b/tests/test_emitters.py
@@ -0,0 +1,107 @@
+from contextlib import closing
+from io import StringIO
+import os
+
+from saucebrush.emitters import (
+ DebugEmitter,
+ CSVEmitter,
+ CountEmitter,
+ SqliteEmitter,
+ SqlDumpEmitter,
+)
+
+
+def test_debug_emitter():
+ with closing(StringIO()) as output:
+ de = DebugEmitter(output)
+ list(de.attach([1, 2, 3]))
+ assert output.getvalue() == "1\n2\n3\n"
+
+
+def test_count_emitter():
+
+ # values for test
+ values = [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ ]
+
+ with closing(StringIO()) as output:
+
+ # test without of parameter
+ ce = CountEmitter(every=10, outfile=output, format="%(count)s records\n")
+ list(ce.attach(values))
+ assert output.getvalue() == "10 records\n20 records\n"
+ ce.done()
+ assert output.getvalue() == "10 records\n20 records\n22 records\n"
+
+ with closing(StringIO()) as output:
+
+ # test with of parameter
+ ce = CountEmitter(every=10, outfile=output, of=len(values))
+ list(ce.attach(values))
+ assert output.getvalue() == "10 of 22\n20 of 22\n"
+ ce.done()
+ assert output.getvalue() == "10 of 22\n20 of 22\n22 of 22\n"
+
+
+def test_csv_emitter():
+ io = StringIO() # if Python 3.x then use StringIO
+
+ with closing(io) as output:
+ ce = CSVEmitter(output, ("x", "y", "z"))
+ list(ce.attach([{"x": 1, "y": 2, "z": 3}, {"x": 5, "y": 5, "z": 5}]))
+ assert output.getvalue() == "x,y,z\r\n1,2,3\r\n5,5,5\r\n"
+
+
+def test_sqlite_emitter():
+
+ import sqlite3
+ import tempfile
+
+ with closing(tempfile.NamedTemporaryFile(suffix=".db")) as f:
+ db_path = f.name
+
+ sle = SqliteEmitter(db_path, "testtable", fieldnames=("a", "b", "c"))
+ list(sle.attach([{"a": "1", "b": "2", "c": "3"}]))
+ sle.done()
+
+ with closing(sqlite3.connect(db_path)) as conn:
+ cur = conn.cursor()
+ cur.execute("""SELECT a, b, c FROM testtable""")
+ results = cur.fetchall()
+
+ os.unlink(db_path)
+
+ assert results == [("1", "2", "3")]
+
+
+def test_sql_dump_emitter():
+
+ with closing(StringIO()) as bffr:
+
+ sde = SqlDumpEmitter(bffr, "testtable", ("a", "b"))
+ list(sde.attach([{"a": 1, "b": "2"}]))
+ sde.done()
+
+ assert bffr.getvalue() == "INSERT INTO `testtable` (`a`,`b`) VALUES (1,'2');\n"
diff --git a/tests/filters.py b/tests/test_filters.py
similarity index 100%
rename from tests/filters.py
rename to tests/test_filters.py
diff --git a/tests/test_recipes.py b/tests/test_recipes.py
new file mode 100644
index 0000000..3a31c6f
--- /dev/null
+++ b/tests/test_recipes.py
@@ -0,0 +1,49 @@
+import pytest
+from saucebrush import Recipe, run_recipe, SaucebrushError, OvercookedError
+from saucebrush.filters import Filter
+
+
+class Raiser(Filter):
+ def process_record(self, record):
+ raise Exception("bad record")
+
+
+class Saver(Filter):
+ def __init__(self):
+ self.saved = []
+
+ def process_record(self, record):
+ self.saved.append(record)
+ return record
+
+
+def test_error_stream():
+ saver = Saver()
+ recipe = Recipe(Raiser(), error_stream=saver)
+ recipe.run([{"a": 1}, {"b": 2}])
+ recipe.done()
+
+ assert saver.saved[0]["record"] == {"a": 1}
+ assert saver.saved[1]["record"] == {"b": 2}
+
+ # Must pass either a Recipe, a Filter or an iterable of Filters
+ # as the error_stream argument
+ assert pytest.raises(SaucebrushError, Recipe, error_stream=5)
+
+
+def test_run_recipe():
+ saver = Saver()
+ run_recipe([1, 2], saver)
+
+ assert saver.saved == [1, 2]
+
+
+def test_done():
+ saver = Saver()
+ recipe = Recipe(saver)
+ recipe.run([1])
+ recipe.done()
+
+ assert pytest.raises(OvercookedError, recipe.run, [2])
+ assert pytest.raises(OvercookedError, recipe.done)
+ assert saver.saved == [1]
diff --git a/tests/test_sources.py b/tests/test_sources.py
new file mode 100644
index 0000000..da6fc1d
--- /dev/null
+++ b/tests/test_sources.py
@@ -0,0 +1,90 @@
+from io import StringIO
+
+from saucebrush.sources import (
+ CSVSource,
+ FixedWidthFileSource,
+ HtmlTableSource,
+ JSONSource,
+)
+
+
+def _get_csv():
+ data = """a,b,c
+1,2,3
+5,5,5
+1,10,100"""
+ return StringIO(data)
+
+
+def test_csv_source_basic():
+ source = CSVSource(_get_csv())
+ expected_data = [
+ {"a": "1", "b": "2", "c": "3"},
+ {"a": "5", "b": "5", "c": "5"},
+ {"a": "1", "b": "10", "c": "100"},
+ ]
+ assert list(source) ==expected_data
+
+
+def test_csv_source_fieldnames():
+ source = CSVSource(_get_csv(), ["x", "y", "z"])
+ expected_data = [
+ {"x": "a", "y": "b", "z": "c"},
+ {"x": "1", "y": "2", "z": "3"},
+ {"x": "5", "y": "5", "z": "5"},
+ {"x": "1", "y": "10", "z": "100"},
+ ]
+ assert list(source) == expected_data
+
+
+def test_csv_source_skiprows():
+ source = CSVSource(_get_csv(), skiprows=1)
+ expected_data = [
+ {"a": "5", "b": "5", "c": "5"},
+ {"a": "1", "b": "10", "c": "100"},
+ ]
+ assert list(source) == expected_data
+
+
+def test_fixed_width_source():
+ data = StringIO("JamesNovember 3 1986\nTim September151999")
+ fields = (("name", 5), ("month", 9), ("day", 2), ("year", 4))
+ source = FixedWidthFileSource(data, fields)
+ expected_data = [
+ {"name": "James", "month": "November", "day": "3", "year": "1986"},
+ {"name": "Tim", "month": "September", "day": "15", "year": "1999"},
+ ]
+ assert list(source) == expected_data
+
+
+def test_json_source():
+
+ content = StringIO("""[{"a": 1, "b": "2", "c": 3}]""")
+
+ js = JSONSource(content)
+ assert list(js) == [{"a": 1, "b": "2", "c": 3}]
+
+
+def test_html_table_source():
+
+ content = StringIO(
+ """
+
+
+
+ a |
+ b |
+ c |
+
+
+ 1 |
+ 2 |
+ 3 |
+
+
+
+ """
+ )
+
+ hts = HtmlTableSource(content, "thetable")
+ assert list(hts) == [{"a": "1", "b": "2", "c": "3"}]
diff --git a/tests/test_stats.py b/tests/test_stats.py
new file mode 100644
index 0000000..6ef4eab
--- /dev/null
+++ b/tests/test_stats.py
@@ -0,0 +1,55 @@
+from saucebrush.stats import Sum, Average, Median, MinMax, StandardDeviation, Histogram
+
+
+def _simple_data():
+ return [
+ {"a": 1, "b": 2, "c": 3},
+ {"a": 5, "b": 5, "c": 5},
+ {"a": 1, "b": 10, "c": 100},
+ ]
+
+
+def test_sum():
+ fltr = Sum("b")
+ list(fltr.attach(_simple_data()))
+ assert fltr.value() == 17
+
+
+def test_average():
+ fltr = Average("c")
+ list(fltr.attach(_simple_data()))
+ assert fltr.value() == 36.0
+
+
+def test_median():
+ # odd number of values
+ fltr = Median("a")
+ list(fltr.attach(_simple_data()))
+ assert fltr.value() == 1
+
+ # even number of values
+ fltr = Median("a")
+ list(fltr.attach(_simple_data()[:2]))
+ assert fltr.value() == 3
+
+
+def test_minmax():
+ fltr = MinMax("b")
+ list(fltr.attach(_simple_data()))
+ assert fltr.value() == (2, 10)
+
+
+def test_standard_deviation():
+ fltr = StandardDeviation("c")
+ list(fltr.attach(_simple_data()))
+ assert fltr.average() == 36.0
+ assert fltr.median() == 5
+ assert fltr.value() == (55.4346462061408, 3073.0)
+ assert fltr.value(True) == (45.2621990922521, 2048.6666666666665)
+
+
+def test_histogram():
+ fltr = Histogram("a")
+ fltr.label_length = 1
+ list(fltr.attach(_simple_data()))
+ assert str(fltr) == "\n1 **\n5 *\n"