Repository: viralogic/py-enumerable (language: python)

Issue:
The result_func from group_by is acting up
The code below prints the correct result, when a breakpoint is inserted on "func":
({'value': 1}, {'key': "{'id': 1}", 'enumerable': '[1, 1, 1]'})
({'value': 2}, {'key': "{'id': 2}", 'enumerable': '[2, 2]'})
({'value': 3}, {'key': "{'id': 3}", 'enumerable': '[3]'})
({'value': 0}, {'key': "{'id': 0}", 'enumerable': '[]'})
If a breakpoint is not planted or better no func(r) but r is used, the "enumerable" comes empty.

code:

def func(res):
    return res

def _010_group_join():
    e1 = Enumerable([
        {'value': 1},
        {'value': 2},
        {'value': 3},
        {'value': 0}
    ])
    e2 = Enumerable([1, 2, 3,1,2,1])
    res = e1.group_join(e2,
        outer_key=lambda x: x['value'],
        inner_key=lambda y: y,
        result_func=lambda r: func(r)).to_list()
    for e in res:
        print(e)
    print('end')
Poetry dependency management
Move dependency management from pipenv to poetry. Thus will remove dependency on pipenv and flit as separate tooling for dependency management and package releasing, respectively.

Incorporate poetry into GitHub action. Also add action for CD into PyPi when pushing release to master branch.
Poetry dependency management
Move dependency management from pipenv to poetry. Thus will remove dependency on pipenv and flit as separate tooling for dependency management and package releasing, respectively.

Incorporate poetry into GitHub action. Also add action for CD into PyPi when pushing release to master branch.


Discussion / hints:
Thank you for the feedback. I will try to replicate this issue when I get a chance.
@haliaga Apologies for the long delay. Just investigating this issue now



Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..df980a2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,37 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Continuous Integration
+
+on:
+  push:
+    branches:
+      - '**'
+      - '!master'
+      - '!release/*'
+  pull_request:
+    branches:
+      - 'development'
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install poetry and dependencies
+      run: |
+        pip install -U pip
+        pip install poetry
+        poetry install
+      env:
+        POETRY_VIRTUALENVS_CREATE: false
+    - name: Lint with flake8
+      run: poetry run flake8 . --ignore=W503,E712,E231,E203 --count --max-complexity=10 --max-line-length=88 --show-source --statistics  --exclude=.git,__pycache__,.venv
+    - name: Test with pytest
+      run: poetry run pytest tests
diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml
new file mode 100644
index 0000000..025f48e
--- /dev/null
+++ b/.github/workflows/prod-cd.yml
@@ -0,0 +1,50 @@
+name: Production Continuous Deployment
+
+on:
+  push:
+    branches:
+      - 'master'
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install poetry and dependencies
+      run: |
+        pip install -U pip
+        pip install poetry
+        poetry install
+      env:
+        POETRY_VIRTUALENVS_CREATE: false
+    - name: Lint with flake8
+      run: poetry run flake8 . --ignore=W503,E712,E231,E203 --count --max-complexity=10 --max-line-length=88 --show-source --statistics  --exclude=.git,__pycache__,.venv
+    - name: Test with pytest
+      run: poetry run pytest tests
+  deploy:
+    needs: test
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install poetry and dependencies
+      run: |
+        pip install -U pip
+        pip install poetry
+        poetry install
+      env:
+        POETRY_VIRTUALENVS_CREATE: false
+    - name: Publish to Test PyPI
+      run: poetry publish --build --username ${{ secrets.PYPI_USERNAME }} --password ${{ secrets.PYPI_PASSWORD }}
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
deleted file mode 100644
index 789d5e9..0000000
--- a/.github/workflows/pythonpackage.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-
-name: Python package
-
-on:
-  push:
-    branches: [ master, development ]
-  pull_request:
-    branches: [ master, development ]
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install future
-    - name: Lint with flake8
-      run: |
-        pip install flake8
-        # stop the build if there are Python syntax errors or undefined names
-        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-    - name: Test with pytest
-      run: |
-        pip install pytest
-        pytest
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 63d0c0e..e0c9b68 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,10 +1,10 @@
 repos:
     -   repo: https://github.com/ambv/black
-        rev: stable
+        rev: 20.8b1
         hooks:
         - id: black
           language_version: python3.7
     -   repo: https://gitlab.com/pycqa/flake8
-        rev: 3.7.9
+        rev: 3.8.4
         hooks:
         - id: flake8
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 47df059..0000000
--- a/Pipfile
+++ /dev/null
@@ -1,21 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-pytest = "*"
-pytest-cov = "<2.6"
-black = "*"
-pre-commit = "*"
-flake8 = "*"
-flit = "*"
-
-[packages]
-future = "*"
-
-[requires]
-python_version = "3.7"
-
-[pipenv]
-allow_prereleases = true
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index aa9858d..0000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,398 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "091cea6413fc43667a6703059ad9154808aced4fa3df5713972bc910f04c301a"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.7"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "future": {
-            "hashes": [
-                "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
-            ],
-            "index": "pypi",
-            "version": "==0.18.2"
-        }
-    },
-    "develop": {
-        "appdirs": {
-            "hashes": [
-                "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
-                "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
-            ],
-            "version": "==1.4.3"
-        },
-        "atomicwrites": {
-            "hashes": [
-                "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
-                "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
-            ],
-            "markers": "sys_platform == 'win32'",
-            "version": "==1.4.0"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
-                "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
-            ],
-            "version": "==19.3.0"
-        },
-        "black": {
-            "hashes": [
-                "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
-                "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
-            ],
-            "index": "pypi",
-            "version": "==19.10b0"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
-                "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
-            ],
-            "version": "==2020.4.5.1"
-        },
-        "cfgv": {
-            "hashes": [
-                "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53",
-                "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"
-            ],
-            "version": "==3.1.0"
-        },
-        "chardet": {
-            "hashes": [
-                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
-                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
-            ],
-            "version": "==3.0.4"
-        },
-        "click": {
-            "hashes": [
-                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
-                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
-            ],
-            "version": "==7.1.2"
-        },
-        "colorama": {
-            "hashes": [
-                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
-                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
-            ],
-            "markers": "sys_platform == 'win32'",
-            "version": "==0.4.3"
-        },
-        "coverage": {
-            "hashes": [
-                "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a",
-                "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355",
-                "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65",
-                "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7",
-                "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9",
-                "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1",
-                "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0",
-                "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55",
-                "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c",
-                "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6",
-                "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef",
-                "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019",
-                "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e",
-                "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0",
-                "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf",
-                "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24",
-                "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2",
-                "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c",
-                "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4",
-                "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0",
-                "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd",
-                "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04",
-                "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e",
-                "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730",
-                "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2",
-                "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768",
-                "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796",
-                "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7",
-                "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a",
-                "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489",
-                "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"
-            ],
-            "version": "==5.1"
-        },
-        "distlib": {
-            "hashes": [
-                "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
-            ],
-            "version": "==0.3.0"
-        },
-        "docutils": {
-            "hashes": [
-                "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
-                "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
-            ],
-            "version": "==0.16"
-        },
-        "filelock": {
-            "hashes": [
-                "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
-                "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
-            ],
-            "version": "==3.0.12"
-        },
-        "flake8": {
-            "hashes": [
-                "sha256:c09e7e4ea0d91fa36f7b8439ca158e592be56524f0b67c39ab0ea2b85ed8f9a4",
-                "sha256:f33c5320eaa459cdee6367016a4bf4ba2a9b81499ce56e6a32abbf0b8d3a2eb4"
-            ],
-            "index": "pypi",
-            "version": "==3.8.0a2"
-        },
-        "flit": {
-            "hashes": [
-                "sha256:017012b809ec489918afd68af7a70bd7c8c770c87b60159d875c126866e97a4b",
-                "sha256:25d184c59cb6f2a83bc3f3651f01007aed12db379b4dc59a4e5765b7aff4730f"
-            ],
-            "index": "pypi",
-            "version": "==2.3.0"
-        },
-        "flit-core": {
-            "hashes": [
-                "sha256:a50bcd8bf5785e3a7d95434244f30ba693e794c5204ac1ee908fc07c4acdbf80",
-                "sha256:a8f8904b534966712390e0a2e434cd33f76037730a0aaed299a286f9e18cac2b"
-            ],
-            "version": "==2.3.0"
-        },
-        "identify": {
-            "hashes": [
-                "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0",
-                "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"
-            ],
-            "version": "==1.4.15"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
-                "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
-            ],
-            "version": "==2.9"
-        },
-        "mccabe": {
-            "hashes": [
-                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
-                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
-            ],
-            "version": "==0.6.1"
-        },
-        "more-itertools": {
-            "hashes": [
-                "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c",
-                "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"
-            ],
-            "version": "==8.2.0"
-        },
-        "nodeenv": {
-            "hashes": [
-                "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"
-            ],
-            "version": "==1.3.5"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
-                "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
-            ],
-            "version": "==20.3"
-        },
-        "pathspec": {
-            "hashes": [
-                "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
-                "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"
-            ],
-            "version": "==0.8.0"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
-                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
-            ],
-            "version": "==0.13.1"
-        },
-        "pre-commit": {
-            "hashes": [
-                "sha256:979b53dab1af35063a483bfe13b0fcbbf1a2cf8c46b60e0a9a8d08e8269647a1",
-                "sha256:f3e85e68c6d1cbe7828d3471896f1b192cfcf1c4d83bf26e26beeb5941855257"
-            ],
-            "index": "pypi",
-            "version": "==2.3.0"
-        },
-        "py": {
-            "hashes": [
-                "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
-                "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
-            ],
-            "version": "==1.8.1"
-        },
-        "pycodestyle": {
-            "hashes": [
-                "sha256:933bfe8d45355fbb35f9017d81fc51df8cb7ce58b82aca2568b870bf7bea1611",
-                "sha256:c1362bf675a7c0171fa5f795917c570c2e405a97e5dc473b51f3656075d73acc"
-            ],
-            "version": "==2.6.0a1"
-        },
-        "pyflakes": {
-            "hashes": [
-                "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
-                "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
-            ],
-            "version": "==2.2.0"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492",
-                "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a"
-            ],
-            "version": "==3.0.0a1"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172",
-                "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"
-            ],
-            "index": "pypi",
-            "version": "==5.4.1"
-        },
-        "pytest-cov": {
-            "hashes": [
-                "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
-                "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
-            ],
-            "index": "pypi",
-            "version": "==2.5.1"
-        },
-        "pytoml": {
-            "hashes": [
-                "sha256:57a21e6347049f73bfb62011ff34cd72774c031b9828cb628a752225136dfc33",
-                "sha256:8eecf7c8d0adcff3b375b09fe403407aa9b645c499e5ab8cac670ac4a35f61e7"
-            ],
-            "version": "==0.1.21"
-        },
-        "pyyaml": {
-            "hashes": [
-                "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
-                "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
-                "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
-                "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
-                "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
-                "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
-                "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
-                "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
-                "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
-                "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
-                "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
-            ],
-            "version": "==5.3.1"
-        },
-        "regex": {
-            "hashes": [
-                "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b",
-                "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8",
-                "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3",
-                "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e",
-                "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683",
-                "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1",
-                "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142",
-                "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3",
-                "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468",
-                "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e",
-                "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3",
-                "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a",
-                "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f",
-                "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6",
-                "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156",
-                "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b",
-                "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db",
-                "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd",
-                "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a",
-                "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948",
-                "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"
-            ],
-            "version": "==2020.4.4"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
-                "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
-            ],
-            "version": "==2.23.0"
-        },
-        "six": {
-            "hashes": [
-                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
-                "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
-            ],
-            "version": "==1.14.0"
-        },
-        "toml": {
-            "hashes": [
-                "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
-                "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
-            ],
-            "version": "==0.10.0"
-        },
-        "typed-ast": {
-            "hashes": [
-                "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
-                "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
-                "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
-                "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
-                "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
-                "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
-                "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
-                "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
-                "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
-                "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
-                "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
-                "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
-                "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
-                "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
-                "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
-                "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
-                "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
-                "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
-                "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
-                "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
-                "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
-            ],
-            "version": "==1.4.1"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
-                "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
-            ],
-            "version": "==1.25.9"
-        },
-        "virtualenv": {
-            "hashes": [
-                "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74",
-                "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"
-            ],
-            "version": "==20.0.20"
-        },
-        "wcwidth": {
-            "hashes": [
-                "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
-                "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
-            ],
-            "version": "==0.1.9"
-        }
-    }
-}
diff --git a/README.md b/README.md
index 4e4ab5a..ab7df1f 100644
--- a/README.md
+++ b/README.md
@@ -29,165 +29,13 @@ Please visit the project [site](https://viralogic.github.io/py-enumerable) for b
 
 ## Contributing ##
 
-Contributions are welcomed. This project uses [pipenv](https://pipenv-fork.readthedocs.io/en/latest/) to handle the few library dependencies. [Pre-commit](https://pre-commit.com/) is also used so that formatting and linting checks are performed on commit.
+Contributions are welcomed. This project uses [poetry](https://python-poetry.org/docs/) to handle the few library dependencies. [Pre-commit](https://pre-commit.com/) is also used so that formatting and linting checks are performed on commit.
 
 1. Clone the repository using `git clone https://github.com/viralogic/py-enumerable.git`
-2. Install pipenv globally `pip install pipenv`
-3. CD into the root of your cloned repository directory and `pipenv install --dev` to install all packages from the repository Pipfile.
-4. Install `pre-commit` by typing `pipenv run pre-commit install`
+2. Install poetry globally as per the instructions [here](https://python-poetry.org/docs/)
+3. CD into the root of your cloned repository directory and `poetry install` to install all packages from the repository Pipfile.
+4. Install `pre-commit` by typing `poetry run pre-commit install`
 5. You should now be ready to start coding!
 
-## Authors ##
-
-[Bruce Fenske](https://github.com/viralogic)
-
-## Contributors ##
-
-1. [Oleg Shilo](https://github.com/oleg-shilo)
-2. [Sebastien Celles](https://github.com/scls19fr)
-3. [Daniel Goltz](https://github.com/dagoltz)
-
-## History ##
-
-<table>
-    <thead>
-        <tr>
-            <th>Date</th>
-            <th style='text-align: right;'>Version</th>
-            <td>Description</td>
-        </tr>
-    </thead>
-    <tbody>
-        <tr>
-            <td>21-Apr-2020</td>
-            <td style='text-align: right;'>1.2.2</td>
-            <td>
-                <ul>
-                    <li>Issue #38 - Fixed issue where to_list calls were taking a very long time. Added some regression testing for this issue</li>
-                    <li>General performance improvements across code-base where identified</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>21-Jan-2020</td>
-            <td style='text-align: right;'>1.2.1</td>
-            <td>
-                <ul>
-                    <li>Issue #36 - Fixed iterating over files</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>24-Nov-2019</td>
-            <td style='text-align: right;'>1.2.0</td>
-            <td>
-                <ul>
-                    <li>Memory consumption improvements by removal of data caching when collection is iterated over</li>
-                    <li>Issue #22 - Unexpected behaviour when using iterator as input data</li>
-                    <li>Issue #34 - Data loss when using any function</li>
-                    <li>Issue #35 - Unexpected result when using first function<li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>7-Oct-2019</td>
-            <td style='text-align: right;'>1.1.0</td>
-            <td>
-                <ul>
-                    <li>Issue #23 - Any accepts a None predicate</li>
-                    <li>Issue #24 - Performance improvement where any returns on first matching element</li>
-                    <li>Issue #25 - Use of any to check for elements in an Enumerable</li>
-                    <li>Issue #26 - Performance improvement where element_at only iterates until the n-th element</li>
-                    <li>Issue #29 - Performance improvement where distinct method is no longer immediately executing.</li>
-                    <li>Issue #30 - Performance improvement where all function is no longer iterating through collection more than once</li>
-                    <li>Issue #31 - Performance improvement where reverse function is no longer immediately executing</li>
-                    <li>Issue #32 - Count function now accepts a lambda predicate to filter collection</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>16-Jul-2019</td>
-            <td style='text-align: right;'>1.0.1</td>
-            <td>
-                <ul>
-                    <li>Issue #21 - Support lambda predicates in first, first_or_default, last, and last_or_default methods</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>07-Mar-2019</td>
-            <td style='text-align: right;'>1.0</td>
-            <td>
-                <ul>
-                    <li>Issue #17 - Added additional LINQ methods to complete the <code>Enumerable</code> API as per <a href="https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=netframework-4.7.2">MSDN</a></li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>06-Jan-2019</td>
-            <td style='text-align: right;'>0.7</td>
-            <td>
-                <ul>
-                    <li>Issue #19 - Distinct bug fix</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>06-Dec-2018</td>
-            <td  style='text-align: right;'>0.6</td>
-            <td>
-                <ul>
-                    <li>Issue #13 - Empty list as default parameter</li>
-                    <li>Issue #14 - except_ method bug fix</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>19-Jul-2017</td>
-            <td style='text-align: right;'>0.5</td>
-            <td>
-                <ul>
-                    <li>last and last_or_default method bug fixes</li>
-                    <li>then_by and then_by_descending implementation</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>30-Jan-2017</td>
-            <td style='text-align: right;'>0.4</td>
-            <td>
-                <ul>
-                    <li>Added Python 3 support</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>15-Jan-2015</td>
-            <td style='text-align: right;'>0.3</td>
-            <td>
-                <ul>
-                    <li>Changed README.rst</li>
-                    <li>Performance improvements</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>12-Jan-2015</td>
-            <td  style='text-align: right;'>0.2</td>
-            <td>
-                <ul>
-                    <li>Added documentation</li>
-                </ul>
-            </td>
-        </tr>
-        <tr>
-            <td>07-Jan-2015</td>
-            <td  style='text-align: right;'>0.1</td>
-            <td>
-                <ul>
-                    <li>Initial beta release</li>
-                </ul>
-            </td>
-        </tr>
-    </tbody>
-</table>
+
+
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..7ab05a5
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,699 @@
+[[package]]
+category = "dev"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "appdirs"
+optional = false
+python-versions = "*"
+version = "1.4.4"
+
+[[package]]
+category = "dev"
+description = "Atomic file writes."
+marker = "sys_platform == \"win32\""
+name = "atomicwrites"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.4.0"
+
+[[package]]
+category = "dev"
+description = "Classes Without Boilerplate"
+name = "attrs"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "20.3.0"
+
+[package.extras]
+dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface"]
+tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+
+[[package]]
+category = "dev"
+description = "The uncompromising code formatter."
+name = "black"
+optional = false
+python-versions = ">=3.6"
+version = "20.8b1"
+
+[package.dependencies]
+appdirs = "*"
+click = ">=7.1.2"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.6,<1"
+regex = ">=2020.1.8"
+toml = ">=0.10.1"
+typed-ast = ">=1.4.0"
+typing-extensions = ">=3.7.4"
+
+[package.dependencies.dataclasses]
+python = "<3.7"
+version = ">=0.6"
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+
+[[package]]
+category = "dev"
+description = "Validate configuration and produce human readable error messages."
+name = "cfgv"
+optional = false
+python-versions = ">=3.6.1"
+version = "3.2.0"
+
+[[package]]
+category = "dev"
+description = "Composable command line interface toolkit"
+name = "click"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "7.1.2"
+
+[[package]]
+category = "dev"
+description = "Cross-platform colored terminal text."
+marker = "sys_platform == \"win32\""
+name = "colorama"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.4.4"
+
+[[package]]
+category = "dev"
+description = "Code coverage measurement for Python"
+name = "coverage"
+optional = false
+python-versions = "*"
+version = "4.4.2"
+
+[[package]]
+category = "dev"
+description = "A backport of the dataclasses module for Python 3.6"
+marker = "python_version < \"3.7\""
+name = "dataclasses"
+optional = false
+python-versions = "*"
+version = "0.6"
+
+[[package]]
+category = "dev"
+description = "Distribution utilities"
+name = "distlib"
+optional = false
+python-versions = "*"
+version = "0.3.1"
+
+[[package]]
+category = "dev"
+description = "A platform independent file lock."
+name = "filelock"
+optional = false
+python-versions = "*"
+version = "3.0.12"
+
+[[package]]
+category = "dev"
+description = "the modular source code checker: pep8 pyflakes and co"
+name = "flake8"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+version = "3.8.4"
+
+[package.dependencies]
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.6.0a1,<2.7.0"
+pyflakes = ">=2.2.0,<2.3.0"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = "*"
+
+[[package]]
+category = "main"
+description = "Clean single-source support for Python 3 and 2"
+name = "future"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "0.18.2"
+
+[[package]]
+category = "dev"
+description = "File identification library for Python"
+name = "identify"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+version = "1.5.9"
+
+[package.extras]
+license = ["editdistance"]
+
+[[package]]
+category = "dev"
+description = "Read metadata from Python packages"
+marker = "python_version < \"3.8\""
+name = "importlib-metadata"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+version = "2.0.0"
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["sphinx", "rst.linker"]
+testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
+
+[[package]]
+category = "dev"
+description = "Read resources from Python packages"
+marker = "python_version < \"3.7\""
+name = "importlib-resources"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+version = "3.3.0"
+
+[package.dependencies]
+[package.dependencies.zipp]
+python = "<3.8"
+version = ">=0.4"
+
+[package.extras]
+docs = ["sphinx", "rst.linker", "jaraco.packaging"]
+
+[[package]]
+category = "dev"
+description = "iniconfig: brain-dead simple config-ini parsing"
+name = "iniconfig"
+optional = false
+python-versions = "*"
+version = "1.1.1"
+
+[[package]]
+category = "dev"
+description = "McCabe checker, plugin for flake8"
+name = "mccabe"
+optional = false
+python-versions = "*"
+version = "0.6.1"
+
+[[package]]
+category = "dev"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+name = "mypy-extensions"
+optional = false
+python-versions = "*"
+version = "0.4.3"
+
+[[package]]
+category = "dev"
+description = "Node.js virtual environment builder"
+name = "nodeenv"
+optional = false
+python-versions = "*"
+version = "1.5.0"
+
+[[package]]
+category = "dev"
+description = "Core utilities for Python packages"
+name = "packaging"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "20.4"
+
+[package.dependencies]
+pyparsing = ">=2.0.2"
+six = "*"
+
+[[package]]
+category = "dev"
+description = "Utility library for gitignore style pattern matching of file paths."
+name = "pathspec"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.8.1"
+
+[[package]]
+category = "dev"
+description = "plugin and hook calling mechanisms for python"
+name = "pluggy"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.13.1"
+
+[package.dependencies]
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+
+[[package]]
+category = "dev"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+name = "pre-commit"
+optional = false
+python-versions = ">=3.6.1"
+version = "2.8.2"
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+toml = "*"
+virtualenv = ">=20.0.8"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = "*"
+
+[package.dependencies.importlib-resources]
+python = "<3.7"
+version = "*"
+
+[[package]]
+category = "dev"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+name = "py"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.9.0"
+
+[[package]]
+category = "dev"
+description = "Python style guide checker"
+name = "pycodestyle"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.6.0"
+
+[[package]]
+category = "dev"
+description = "passive checker of Python programs"
+name = "pyflakes"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.2.0"
+
+[[package]]
+category = "dev"
+description = "Python parsing module"
+name = "pyparsing"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "2.4.7"
+
+[[package]]
+category = "dev"
+description = "pytest: simple powerful testing with Python"
+name = "pytest"
+optional = false
+python-versions = ">=3.5"
+version = "6.1.2"
+
+[package.dependencies]
+atomicwrites = ">=1.0"
+attrs = ">=17.4.0"
+colorama = "*"
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<1.0"
+py = ">=1.8.2"
+toml = "*"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12"
+
+[package.extras]
+checkqa_mypy = ["mypy (0.780)"]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+category = "dev"
+description = "Pytest plugin for measuring coverage."
+name = "pytest-cov"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.10.1"
+
+[package.dependencies]
+coverage = ">=4.4"
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+category = "dev"
+description = "YAML parser and emitter for Python"
+name = "pyyaml"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "5.3.1"
+
+[[package]]
+category = "dev"
+description = "Alternative regular expression module, to replace re."
+name = "regex"
+optional = false
+python-versions = "*"
+version = "2020.11.13"
+
+[[package]]
+category = "dev"
+description = "Python 2 and 3 compatibility utilities"
+name = "six"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "1.15.0"
+
+[[package]]
+category = "dev"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "0.10.2"
+
+[[package]]
+category = "dev"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+name = "typed-ast"
+optional = false
+python-versions = "*"
+version = "1.4.1"
+
+[[package]]
+category = "dev"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+name = "typing-extensions"
+optional = false
+python-versions = "*"
+version = "3.7.4.3"
+
+[[package]]
+category = "dev"
+description = "Virtual Python Environment builder"
+name = "virtualenv"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+version = "20.1.0"
+
+[package.dependencies]
+appdirs = ">=1.4.3,<2"
+distlib = ">=0.3.1,<1"
+filelock = ">=3.0.0,<4"
+six = ">=1.9.0,<2"
+
+[package.dependencies.importlib-metadata]
+python = "<3.8"
+version = ">=0.12,<3"
+
+[package.dependencies.importlib-resources]
+python = "<3.7"
+version = ">=1.0"
+
+[package.extras]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
+
+[[package]]
+category = "dev"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+marker = "python_version < \"3.8\""
+name = "zipp"
+optional = false
+python-versions = ">=3.6"
+version = "3.4.0"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+
+[metadata]
+content-hash = "f184eda8cf12fd70ecdbeceea571197b1bd29e2af662b6073679ca17170787c8"
+lock-version = "1.0"
+python-versions = ">=3.6.1"
+
+[metadata.files]
+appdirs = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+atomicwrites = [
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
+attrs = [
+    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
+    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
+]
+black = [
+    {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"},
+    {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
+]
+cfgv = [
+    {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
+    {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
+]
+click = [
+    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
+    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+]
+colorama = [
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coverage = [
+    {file = "coverage-4.4.2-cp26-cp26m-macosx_10_10_x86_64.whl", hash = "sha256:d1ee76f560c3c3e8faada866a07a32485445e16ed2206ac8378bd90dadffb9f0"},
+    {file = "coverage-4.4.2-cp26-cp26m-manylinux1_i686.whl", hash = "sha256:007eeef7e23f9473622f7d94a3e029a45d55a92a1f083f0f3512f5ab9a669b05"},
+    {file = "coverage-4.4.2-cp26-cp26m-manylinux1_x86_64.whl", hash = "sha256:17307429935f96c986a1b1674f78079528833410750321d22b5fb35d1883828e"},
+    {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_i686.whl", hash = "sha256:845fddf89dca1e94abe168760a38271abfc2e31863fbb4ada7f9a99337d7c3dc"},
+    {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_x86_64.whl", hash = "sha256:3f4d0b3403d3e110d2588c275540649b1841725f5a11a7162620224155d00ba2"},
+    {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_intel.whl", hash = "sha256:4c4f368ffe1c2e7602359c2c50233269f3abe1c48ca6b288dcd0fb1d1c679733"},
+    {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:f8c55dd0f56d3d618dfacf129e010cbe5d5f94b6951c1b2f13ab1a2f79c284da"},
+    {file = "coverage-4.4.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cdd92dd9471e624cd1d8c1a2703d25f114b59b736b0f1f659a98414e535ffb3d"},
+    {file = "coverage-4.4.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ad357d12971e77360034c1596011a03f50c0f9e1ecd12e081342b8d1aee2236"},
+    {file = "coverage-4.4.2-cp27-cp27m-win32.whl", hash = "sha256:700d7579995044dc724847560b78ac786f0ca292867447afda7727a6fbaa082e"},
+    {file = "coverage-4.4.2-cp27-cp27m-win_amd64.whl", hash = "sha256:66f393e10dd866be267deb3feca39babba08ae13763e0fc7a1063cbe1f8e49f6"},
+    {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9a0e1caed2a52f15c96507ab78a48f346c05681a49c5b003172f8073da6aa6b"},
+    {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:eea9135432428d3ca7ee9be86af27cb8e56243f73764a9b6c3e0bda1394916be"},
+    {file = "coverage-4.4.2-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:5ff16548492e8a12e65ff3d55857ccd818584ed587a6c2898a9ebbe09a880674"},
+    {file = "coverage-4.4.2-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:d00e29b78ff610d300b2c37049a41234d48ea4f2d2581759ebcf67caaf731c31"},
+    {file = "coverage-4.4.2-cp33-cp33m-manylinux1_x86_64.whl", hash = "sha256:87d942863fe74b1c3be83a045996addf1639218c2cb89c5da18c06c0fe3917ea"},
+    {file = "coverage-4.4.2-cp34-cp34m-macosx_10_10_x86_64.whl", hash = "sha256:358d635b1fc22a425444d52f26287ae5aea9e96e254ff3c59c407426f44574f4"},
+    {file = "coverage-4.4.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:81912cfe276e0069dca99e1e4e6be7b06b5fc8342641c6b472cb2fed7de7ae18"},
+    {file = "coverage-4.4.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:079248312838c4c8f3494934ab7382a42d42d5f365f0cf7516f938dbb3f53f3f"},
+    {file = "coverage-4.4.2-cp34-cp34m-win32.whl", hash = "sha256:b0059630ca5c6b297690a6bf57bf2fdac1395c24b7935fd73ee64190276b743b"},
+    {file = "coverage-4.4.2-cp34-cp34m-win_amd64.whl", hash = "sha256:493082f104b5ca920e97a485913de254cbe351900deed72d4264571c73464cd0"},
+    {file = "coverage-4.4.2-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:e3ba9b14607c23623cf38f90b23f5bed4a3be87cbfa96e2e9f4eabb975d1e98b"},
+    {file = "coverage-4.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:82cbd3317320aa63c65555aa4894bf33a13fb3a77f079059eb5935eea415938d"},
+    {file = "coverage-4.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9721f1b7275d3112dc7ccf63f0553c769f09b5c25a26ee45872c7f5c09edf6c1"},
+    {file = "coverage-4.4.2-cp35-cp35m-win32.whl", hash = "sha256:bd4800e32b4c8d99c3a2c943f1ac430cbf80658d884123d19639bcde90dad44a"},
+    {file = "coverage-4.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:f29841e865590af72c4b90d7b5b8e93fd560f5dea436c1d5ee8053788f9285de"},
+    {file = "coverage-4.4.2-cp36-cp36m-macosx_10_12_x86_64.whl", hash = "sha256:f3a5c6d054c531536a83521c00e5d4004f1e126e2e2556ce399bef4180fbe540"},
+    {file = "coverage-4.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd707a21332615108b736ef0b8513d3edaf12d2a7d5fc26cd04a169a8ae9b526"},
+    {file = "coverage-4.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2e1a5c6adebb93c3b175103c2f855eda957283c10cf937d791d81bef8872d6ca"},
+    {file = "coverage-4.4.2-cp36-cp36m-win32.whl", hash = "sha256:f87f522bde5540d8a4b11df80058281ac38c44b13ce29ced1e294963dd51a8f8"},
+    {file = "coverage-4.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a7cfaebd8f24c2b537fa6a271229b051cdac9c1734bb6f939ccfc7c055689baa"},
+    {file = "coverage-4.4.2.tar.gz", hash = "sha256:309d91bd7a35063ec7a0e4d75645488bfab3f0b66373e7722f23da7f5b0f34cc"},
+    {file = "coverage-4.4.2.win-amd64-py2.7.exe", hash = "sha256:b6cebae1502ce5b87d7c6f532fa90ab345cfbda62b95aeea4e431e164d498a3d"},
+    {file = "coverage-4.4.2.win-amd64-py3.4.exe", hash = "sha256:a4497faa4f1c0fc365ba05eaecfb6b5d24e3c8c72e95938f9524e29dadb15e76"},
+    {file = "coverage-4.4.2.win-amd64-py3.5.exe", hash = "sha256:2b4d7f03a8a6632598cbc5df15bbca9f778c43db7cf1a838f4fa2c8599a8691a"},
+    {file = "coverage-4.4.2.win-amd64-py3.6.exe", hash = "sha256:1afccd7e27cac1b9617be8c769f6d8a6d363699c9b86820f40c74cfb3328921c"},
+    {file = "coverage-4.4.2.win32-py2.7.exe", hash = "sha256:0388c12539372bb92d6dde68b4627f0300d948965bbb7fc104924d715fdc0965"},
+    {file = "coverage-4.4.2.win32-py3.4.exe", hash = "sha256:ab3508df9a92c1d3362343d235420d08e2662969b83134f8a97dc1451cbe5e84"},
+    {file = "coverage-4.4.2.win32-py3.5.exe", hash = "sha256:43a155eb76025c61fc20c3d03b89ca28efa6f5be572ab6110b2fb68eda96bfea"},
+    {file = "coverage-4.4.2.win32-py3.6.exe", hash = "sha256:f98b461cb59f117887aa634a66022c0bd394278245ed51189f63a036516e32de"},
+]
+dataclasses = [
+    {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"},
+    {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"},
+]
+distlib = [
+    {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
+    {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
+]
+filelock = [
+    {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
+    {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
+]
+flake8 = [
+    {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"},
+    {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"},
+]
+future = [
+    {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
+]
+identify = [
+    {file = "identify-1.5.9-py2.py3-none-any.whl", hash = "sha256:5dd84ac64a9a115b8e0b27d1756b244b882ad264c3c423f42af8235a6e71ca12"},
+    {file = "identify-1.5.9.tar.gz", hash = "sha256:c9504ba6a043ee2db0a9d69e43246bc138034895f6338d5aed1b41e4a73b1513"},
+]
+importlib-metadata = [
+    {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"},
+    {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"},
+]
+importlib-resources = [
+    {file = "importlib_resources-3.3.0-py2.py3-none-any.whl", hash = "sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"},
+    {file = "importlib_resources-3.3.0.tar.gz", hash = "sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+nodeenv = [
+    {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
+    {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"},
+]
+packaging = [
+    {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
+    {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
+]
+pathspec = [
+    {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
+    {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
+]
+pluggy = [
+    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+pre-commit = [
+    {file = "pre_commit-2.8.2-py2.py3-none-any.whl", hash = "sha256:22e6aa3bd571debb01eb7d34483f11c01b65237be4eebbf30c3d4fb65762d315"},
+    {file = "pre_commit-2.8.2.tar.gz", hash = "sha256:905ebc9b534b991baec87e934431f2d0606ba27f2b90f7f652985f5a5b8b6ae6"},
+]
+py = [
+    {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
+    {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
+    {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
+]
+pyflakes = [
+    {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
+    {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
+]
+pyparsing = [
+    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
+    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+]
+pytest = [
+    {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"},
+    {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"},
+]
+pytest-cov = [
+    {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
+    {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
+]
+pyyaml = [
+    {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
+    {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
+    {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
+    {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
+    {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
+    {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
+    {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
+    {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
+    {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
+    {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
+    {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
+]
+regex = [
+    {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"},
+    {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"},
+    {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"},
+    {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"},
+    {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"},
+    {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"},
+    {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"},
+    {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"},
+    {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"},
+    {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"},
+    {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"},
+    {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
+    {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
+]
+six = [
+    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
+    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+typed-ast = [
+    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
+    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
+    {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
+    {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
+    {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
+    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
+    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
+    {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
+    {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
+    {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
+    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
+    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
+    {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
+    {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
+    {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
+    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
+    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
+    {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
+    {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
+    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
+    {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
+]
+typing-extensions = [
+    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
+    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
+    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
+]
+virtualenv = [
+    {file = "virtualenv-20.1.0-py2.py3-none-any.whl", hash = "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2"},
+    {file = "virtualenv-20.1.0.tar.gz", hash = "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380"},
+]
+zipp = [
+    {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
+    {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
+]
diff --git a/py_linq/py_linq.py b/py_linq/py_linq.py
index cf53797..82fe2bc 100644
--- a/py_linq/py_linq.py
+++ b/py_linq/py_linq.py
@@ -84,7 +84,7 @@ def select(self, func=lambda x: x):
         :param func: lambda expression on how to perform transformation
         :return: new Enumerable object containing transformed data
         """
-        return SelectEnumerable(self, func)
+        return SelectEnumerable(Enumerable(iter(self)), func)
 
     def sum(self, func=lambda x: x):
         """
@@ -221,7 +221,7 @@ def order_by(self, key):
         if key is None:
             raise NullArgumentError(u"No key for sorting given")
         kf = [OrderingDirection(key, reverse=False)]
-        return SortedEnumerable(self, key_funcs=kf)
+        return SortedEnumerable(Enumerable(iter(self)), key_funcs=kf)
 
     def order_by_descending(self, key):
         """
@@ -232,7 +232,7 @@ def order_by_descending(self, key):
         if key is None:
             raise NullArgumentError(u"No key for sorting given")
         kf = [OrderingDirection(key, reverse=True)]
-        return SortedEnumerable(self, key_funcs=kf)
+        return SortedEnumerable(Enumerable(iter(self)), key_funcs=kf)
 
     def skip(self, n):
         """
@@ -240,7 +240,7 @@ def skip(self, n):
         :param n: Number of elements to skip as int
         :return: new Enumerable object
         """
-        return SkipEnumerable(self, n)
+        return SkipEnumerable(Enumerable(iter(self)), n)
 
     def take(self, n):
         """
@@ -248,7 +248,7 @@ def take(self, n):
         :param n: Number of elements to take
         :return: new Enumerable object
         """
-        return TakeEnumerable(self, n)
+        return TakeEnumerable(Enumerable(iter(self)), n)
 
     def where(self, predicate):
         """
@@ -258,7 +258,7 @@ def where(self, predicate):
         """
         if predicate is None:
             raise NullArgumentError("No predicate given for where clause")
-        return WhereEnumerable(self, predicate)
+        return WhereEnumerable(Enumerable(iter(self)), predicate)
 
     def single(self, predicate=None):
         """
@@ -298,7 +298,7 @@ def select_many(self, func=lambda x: x):
         :param func: selector as lambda expression
         :return: new Enumerable object
         """
-        return SelectManyEnumerable(self, func)
+        return SelectManyEnumerable(Enumerable(iter(self)), func)
 
     def add(self, element):
         """
@@ -318,7 +318,9 @@ def concat(self, enumerable):
         """
         if not isinstance(enumerable, Enumerable):
             raise TypeError(u"enumerable argument must be an instance of Enumerable")
-        return ConcatenateEnumerable(self, enumerable)
+        concatenated = ConcatenateEnumerable(Enumerable(iter(self)), enumerable)
+        self._iterable = concatenated
+        return concatenated
 
     def group_by(self, key_names=[], key=lambda x: x, result_func=lambda x: x):
         """
@@ -353,7 +355,7 @@ def group_by(self, key_names=[], key=lambda x: x, result_func=lambda x: x):
         :param result_func: transformation function as lambda expression
         :return: Enumerable of grouping objects
         """
-        return GroupedEnumerable(self, key, key_names, result_func)
+        return GroupedEnumerable(Enumerable(iter(self)), key, key_names, result_func)
 
     def distinct(self, key=lambda x: x):
         """
@@ -362,7 +364,9 @@ def distinct(self, key=lambda x: x):
         :param key: key selector as lambda expression
         :return: new Enumerable object
         """
-        return GroupedEnumerable(self, key, ["distinct"], lambda g: g.first())
+        return GroupedEnumerable(
+            Enumerable(iter(self)), key, ["distinct"], lambda g: g.first()
+        )
 
     def join(
         self,
@@ -383,7 +387,9 @@ def join(
             raise TypeError(
                 u"inner_enumerable parameter must be an instance of Enumerable"
             )
-        return JoinEnumerable(self, inner_enumerable, outer_key, inner_key, result_func)
+        return JoinEnumerable(
+            Enumerable(iter(self)), inner_enumerable, outer_key, inner_key, result_func
+        )
 
     def default_if_empty(self, value=None):
         """
@@ -415,9 +421,27 @@ def group_join(
             raise TypeError(
                 u"inner enumerable parameter must be an instance of Enumerable"
             )
-        return GroupJoinEnumerable(
-            self, inner_enumerable, outer_key, inner_key, result_func
+        group_joined = (
+            Enumerable(iter(self))
+            .join(
+                inner_enumerable=inner_enumerable,
+                outer_key=outer_key,
+                inner_key=inner_key,
+                result_func=lambda e: (e[0], e[1]),
+            )
+            .group_by(
+                key_names=["id"],
+                key=lambda t: outer_key(t[0]),
+                result_func=lambda g: (
+                    g.first()[0],
+                    g.where(lambda i: inner_key(i[1]) == g.key.id).select(
+                        lambda i: i[1]
+                    ),
+                ),
+            )
+            .select(lambda gj: result_func(gj))
         )
+        return group_joined
 
     def any(self, predicate=None):
         """
@@ -437,7 +461,7 @@ def intersect(self, enumerable, key=lambda x: x):
         """
         if not isinstance(enumerable, Enumerable):
             raise TypeError(u"enumerable parameter must be an instance of Enumerable")
-        return IntersectEnumerable(self, enumerable, key)
+        return IntersectEnumerable(Enumerable(iter(self)), enumerable, key)
 
     def aggregate(self, func, seed=None):
         """
@@ -467,7 +491,7 @@ def union(self, enumerable, key=lambda x: x):
         """
         if not isinstance(enumerable, Enumerable):
             raise TypeError(u"enumerable parameter must be an instance of Enumerable")
-        return UnionEnumerable(self, enumerable, key)
+        return UnionEnumerable(Enumerable(iter(self)), enumerable, key)
 
     def except_(self, enumerable, key=lambda x: x):
         """
@@ -478,7 +502,7 @@ def except_(self, enumerable, key=lambda x: x):
         """
         if not isinstance(enumerable, Enumerable):
             raise TypeError(u"enumerable parameter must be an instance of Enumerable")
-        return ExceptEnumerable(self, enumerable, key)
+        return ExceptEnumerable(Enumerable(iter(self)), enumerable, key)
 
     def contains(self, element, key=lambda x: x):
         """
@@ -547,7 +571,7 @@ def reverse(self):
         Inverts the order of the elements in a sequence
         :return: Enumerable with elements in reversed order
         """
-        return ReversedEnumerable(self)
+        return ReversedEnumerable(Enumerable(iter(self)))
 
     def skip_last(self, n):
         """
@@ -564,7 +588,7 @@ def skip_while(self, predicate):
         :param predicate: a predicate as a lambda expression
         :return: Enumerable
         """
-        return SkipWhileEnumerable(self, predicate)
+        return SkipWhileEnumerable(Enumerable(iter(self)), predicate)
 
     def take_last(self, n):
         """
@@ -581,7 +605,7 @@ def take_while(self, predicate):
         :param predicate: a predicate as a lambda expression
         :return: Enumerable
         """
-        return TakeWhileEnumerable(self, predicate)
+        return TakeWhileEnumerable(Enumerable(iter(self)), predicate)
 
     def zip(self, enumerable, func=lambda x: x):
         """
@@ -593,7 +617,7 @@ def zip(self, enumerable, func=lambda x: x):
         """
         if not isinstance(enumerable, Enumerable):
             raise TypeError()
-        return ZipEnumerable(self, enumerable, func)
+        return ZipEnumerable(Enumerable(iter(self)), enumerable, func)
 
 
 class SelectEnumerable(Enumerable):
@@ -728,7 +752,10 @@ def __init__(self, enumerable1, enumerable2):
         self.enumerable = enumerable2
 
     def __iter__(self):
-        return itertools.chain(iter(self._iterable), self.enumerable)
+        for i in iter(self._iterable):
+            yield i
+        for j in iter(self.enumerable):
+            yield j
 
     def __getitem__(self, n):
         if n < 0:
@@ -823,9 +850,11 @@ def _load_data(self):
                 self.grouping[kv_hash].add(d)
 
     def _can_enumerate(self, key_value):
-        return hasattr(key_value, "__len__") \
-            and len(key_value) > 0 \
+        return (
+            hasattr(key_value, "__len__")
+            and len(key_value) > 0
             and not isinstance(key_value, string_types)
+        )
 
     def __iter__(self):
         for k in self.grouping:
@@ -963,32 +992,3 @@ def __iter__(self):
                 ik = self.inner_key(inner)
                 if ok == ik:
                     yield self.result_func((outer, inner))
-
-
-class GroupJoinEnumerable(Enumerable):
-    """
-    Class to hold state for performing group join
-    """
-
-    def __init__(
-        self, outer_enumerable, inner_enumerable, outer_key, inner_key, result_func
-    ):
-        super(GroupJoinEnumerable, self).__init__(outer_enumerable)
-        self.inner_enumerable = inner_enumerable
-        self.outer_key = outer_key
-        self.inner_key = inner_key
-        self.result_func = result_func
-
-    def __iter__(self):
-        for o in iter(self._iterable):
-            ok = self.outer_key(o)
-            result = self.result_func(
-                (
-                    o,
-                    Grouping(
-                        Key({"id": ok}),
-                        self.inner_enumerable.where(lambda i: self.inner_key(i) == ok),
-                    ),
-                )
-            )
-            yield result
diff --git a/pyproject.toml b/pyproject.toml
index 33cf882..0a398b2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,27 +1,25 @@
-[build-system]
-requires = ["flit"]
-build-backend = "flit.buildapi"
+[tool.poetry]
+name = "py-linq"
+version = "1.2.5"
+description = "LINQ (Language Integrated Query) is a popular querying language available in .NET. This library ports the language so that developers can query collections of objects using the same syntax. This library would be useful for Python developers with experience using the expressiveness and power of LINQ."
+authors = ["Bruce Fenske <bwfenske@ualberta.ca>"]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/viralogic/py-enumerable"
+homepage = "https://github.com/viralogic/py-enumerable"
+keywords = ["LINQ", "py-linq"]
+
+[tool.poetry.dependencies]
+python = ">=3.6.1"
+future = "^0.18.2"
 
-[tool.flit.metadata]
-module = "py_linq"
-author = "Bruce Fenske"
-author-email = "bwfenske@ualberta.ca"
-home-page = "https://github.com/viralogic/py-enumerable"
-description-file="README.md"
-requires=["future"]
-classifiers=[
-        'Development Status :: 4 - Beta',
-        'Intended Audience :: Developers',
-        'Natural Language :: English',
-        'License :: OSI Approved :: MIT License',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
-        'Programming Language :: Python :: 3.6',
-        'Topic :: Software Development :: Libraries :: Python Modules'
-    ]
+[tool.poetry.dev-dependencies]
+pytest = "^6.1.2"
+pytest-cov = "^2.10.1"
+black = "^20.8b1"
+pre-commit = "^2.8.2"
+flake8 = "^3.8.4"
 
+[build-system]
+requires = ["poetry>=0.12"]
+build-backend = "poetry.masonry.api"


Test patch:
diff --git a/.github/workflows/test-cd.yml b/.github/workflows/test-cd.yml
new file mode 100644
index 0000000..d097bfd
--- /dev/null
+++ b/.github/workflows/test-cd.yml
@@ -0,0 +1,50 @@
+name: Test Continuous Deployment
+
+on:
+  push:
+    branches:
+      - 'release/*'
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install poetry and dependencies
+      run: |
+        pip install -U pip
+        pip install poetry
+        poetry install
+      env:
+        POETRY_VIRTUALENVS_CREATE: false
+    - name: Lint with flake8
+      run: poetry run flake8 . --ignore=W503,E712,E231,E203 --count --max-complexity=10 --max-line-length=88 --show-source --statistics  --exclude=.git,__pycache__,.venv
+    - name: Test with pytest
+      run: poetry run pytest tests
+  deploy:
+    needs: test
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6]
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install poetry and dependencies
+      run: |
+        pip install -U pip
+        pip install poetry
+        poetry install
+      env:
+        POETRY_VIRTUALENVS_CREATE: false
+    - name: Publish to Test PyPI
+      run: poetry publish --build --username ${{ secrets.PYPI_USERNAME }} --password ${{ secrets.PYPI_PASSWORD }} --dry-run
diff --git a/tests/test_functions.py b/tests/test_functions.py
index 7a6f379..ac9cc19 100644
--- a/tests/test_functions.py
+++ b/tests/test_functions.py
@@ -1,3 +1,4 @@
+from py_linq.py_linq import SelectEnumerable, WhereEnumerable
 from unittest import TestCase
 from py_linq import Enumerable
 from tests import _empty, _simple, _complex, _locations
@@ -73,18 +74,26 @@ def test_count_with_filter(self):
         self.assertEqual(self.complex.count(lambda x: x["value"] > 1), 2)
 
     def test_select(self):
-        self.assertListEqual([], self.empty.select(lambda x: x["value"]).to_list())
+        self.assertListEqual(
+            [], Enumerable.empty().select(lambda x: x["value"]).to_list()
+        )
         self.assertListEqual(
             [{"value": 1}, {"value": 2}, {"value": 3}],
-            self.simple.select(lambda x: {"value": x}).to_list(),
+            Enumerable([1, 2, 3]).select(lambda x: {"value": x}).to_list(),
         )
         self.assertListEqual(
-            [1, 2, 3], self.complex.select(lambda x: x["value"]).to_list()
+            [1, 2, 3],
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            )
+            .select(lambda x: x["value"])
+            .to_list(),
         )
 
-        for i, e in enumerate(self.complex.select(lambda x: x["value"])):
-            self.assertEqual(i + 1, e)
-
     def test_min(self):
         self.assertRaises(NoElementsError, self.empty.min)
         self.assertEqual(1, self.simple.min())
@@ -192,9 +201,9 @@ def test_median(self):
         self.assertEqual(median, self.complex.median(lambda x: x["value"]))
 
     def test_skip(self):
-        self.assertListEqual([], self.empty.skip(2).to_list())
-        self.assertListEqual([], self.simple.skip(3).to_list())
-        self.assertListEqual([2, 3], self.simple.skip(1).to_list())
+        self.assertListEqual([], Enumerable().skip(2).to_list())
+        self.assertListEqual([], Enumerable([1, 2, 3]).skip(3).to_list())
+        self.assertListEqual([2, 3], Enumerable([1, 2, 3]).skip(1).to_list())
 
     def test_take(self):
         self.assertListEqual(_simple, self.simple.take(4).to_list())
@@ -218,7 +227,8 @@ def test_filter(self):
     def test_select_with_filter(self):
         self.assertListEqual(
             [2],
-            self.complex.where(lambda x: x["value"] == 2)
+            Enumerable([{"value": 1}, {"value": 2}, {"value": 3}])
+            .where(lambda x: x["value"] == 2)
             .select(lambda x: x["value"])
             .to_list(),
         )
@@ -268,53 +278,142 @@ def test_single_or_default(self):
         )
 
     def test_select_many(self):
-        empty = Enumerable([[], [], []])
-        simple = Enumerable([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
-        __complex = Enumerable(
-            [
-                {"key": 1, "values": [1, 2, 3]},
-                {"key": 2, "values": [4, 5, 6]},
-                {"key": 3, "values": [7, 8, 9]},
-            ]
-        )
-
-        self.assertListEqual([], empty.select_many().to_list())
+        self.assertListEqual([], Enumerable([[], [], []]).select_many().to_list())
         self.assertListEqual(
-            [1, 2, 3, 4, 5, 6, 7, 8, 9], simple.select_many().to_list()
+            [1, 2, 3, 4, 5, 6, 7, 8, 9],
+            Enumerable([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).select_many().to_list(),
+        )
+        self.assertEqual(
+            9, Enumerable([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).select_many().count()
         )
-        self.assertEqual(9, simple.select_many().count())
         self.assertListEqual(
             [1, 2, 3, 4, 5, 6, 7, 8, 9],
-            __complex.select_many(lambda x: x["values"]).to_list(),
+            Enumerable(
+                [
+                    {"key": 1, "values": [1, 2, 3]},
+                    {"key": 2, "values": [4, 5, 6]},
+                    {"key": 3, "values": [7, 8, 9]},
+                ]
+            )
+            .select_many(lambda x: x["values"])
+            .to_list(),
+        )
+        self.assertEqual(
+            9,
+            Enumerable(
+                [
+                    {"key": 1, "values": [1, 2, 3]},
+                    {"key": 2, "values": [4, 5, 6]},
+                    {"key": 3, "values": [7, 8, 9]},
+                ]
+            )
+            .select_many(lambda x: x["values"])
+            .count(),
         )
-        self.assertEqual(9, __complex.select_many(lambda x: x["values"]).count())
 
     def test_concat(self):
-        self.assertListEqual([], self.empty.concat(self.empty).to_list())
-        self.assertListEqual(_simple, self.empty.concat(self.simple).to_list())
-        self.assertListEqual(_simple, self.simple.concat(self.empty).to_list())
+        self.assertListEqual([], Enumerable().concat(Enumerable()).to_list())
+        self.assertListEqual(
+            [1, 2, 3], Enumerable().concat(Enumerable([1, 2, 3])).to_list()
+        )
+        self.assertListEqual(
+            [1, 2, 3], Enumerable([1, 2, 3]).concat(Enumerable()).to_list()
+        )
         self.assertListEqual(
             [1, 2, 3, 1, 2, 3],
-            self.simple.concat(self.complex.select(lambda c: c["value"])).to_list(),
+            Enumerable([1, 2, 3])
+            .concat(
+                Enumerable(
+                    [
+                        {"value": 1},
+                        {"value": 2},
+                        {"value": 3},
+                    ]
+                ).select(lambda c: c["value"])
+            )
+            .to_list(),
         )
         self.assertListEqual(
             [1, 2, 3, 1, 2, 3],
-            self.complex.select(lambda c: c["value"]).concat(self.simple).to_list(),
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            )
+            .select(lambda c: c["value"])
+            .concat(Enumerable([1, 2, 3]))
+            .to_list(),
+        )
+        self.assertListEqual(
+            [1, 2, 3, {"value": 1}, {"value": 2}, {"value": 3}],
+            Enumerable([1, 2, 3])
+            .concat(
+                Enumerable(
+                    [
+                        {"value": 1},
+                        {"value": 2},
+                        {"value": 3},
+                    ]
+                )
+            )
+            .to_list(),
+        )
+
+    def test_concat_mutating(self):
+        empty = Enumerable()
+        empty.concat(Enumerable())
+        self.assertListEqual([], empty.to_list())
+
+        simple = Enumerable()
+        simple.concat(Enumerable([1, 2, 3]))
+        self.assertListEqual([1, 2, 3], simple.to_list())
+
+        double_simple = Enumerable([1, 2, 3])
+        double_simple.concat(
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            ).select(lambda c: c["value"])
+        )
+        self.assertListEqual([1, 2, 3, 1, 2, 3], double_simple.to_list())
+
+        simple_complex = Enumerable([1, 2, 3])
+        simple_complex.concat(
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            )
         )
         self.assertListEqual(
             [1, 2, 3, {"value": 1}, {"value": 2}, {"value": 3}],
-            self.simple.concat(self.complex).to_list(),
+            simple_complex.to_list(),
         )
 
+    def test_multiple_concatenate(self):
+        simple = Enumerable([1, 2, 3])
+        simple.add(4)
+        self.assertEqual(4, simple.count())
+        simple.add(5)
+        self.assertEqual(5, simple.count())
+        self.assertEqual(5, simple.last())
+
     def test_group_by(self):
-        simple_grouped = self.simple.group_by(key_names=["id"])
+        simple_grouped = Enumerable([1, 2, 3]).group_by(key_names=["id"])
         self.assertEqual(3, simple_grouped.count())
         second = simple_grouped.single(lambda s: s.key.id == 2)
         self.assertListEqual([2], second.to_list())
 
-        complex_grouped = self.complex.group_by(
-            key_names=["value"], key=lambda x: x["value"]
-        )
+        complex_grouped = Enumerable(
+            [{"value": 1}, {"value": 2}, {"value": 3}]
+        ).group_by(key_names=["value"], key=lambda x: x["value"])
         self.assertEqual(complex_grouped.count(), 3)
         self.assertListEqual(
             [1, 2, 3],
@@ -334,9 +433,11 @@ def test_group_by(self):
         self.assertEqual(240000, london.sum(lambda c: c[3]))
 
     def test_distinct(self):
-        self.assertListEqual([], self.empty.distinct().to_list())
+        self.assertListEqual([], Enumerable.empty().distinct().to_list())
         six.assertCountEqual(
-            self, _simple, self.simple.concat(self.simple).distinct().to_list()
+            self,
+            [1, 2, 3],
+            Enumerable([1, 2, 3]).concat(Enumerable([1, 2, 3])).distinct().to_list(),
         )
 
         locations = Enumerable(_locations).distinct(lambda x: x[0])
@@ -348,15 +449,23 @@ def test_default_if_empty(self):
         six.assertCountEqual(self, _complex, self.complex.default_if_empty().to_list())
 
     def test_any(self):
-        self.assertFalse(self.empty.any(lambda x: x == 1))
-        self.assertFalse(self.empty.any())
+        self.assertFalse(Enumerable.empty().any(lambda x: x == 1))
+        self.assertFalse(Enumerable.empty().any())
 
-        self.assertTrue(self.simple.any(lambda x: x == 1))
-        self.assertTrue(self.simple.any())
+        self.assertTrue(Enumerable([1, 2, 3]).any(lambda x: x == 1))
+        self.assertTrue(Enumerable([1, 2, 3]).any())
 
-        self.assertTrue(self.complex.any())
-        self.assertFalse(self.complex.any(lambda x: x["value"] < 1))
-        self.assertTrue(self.complex.any(lambda x: x["value"] >= 1))
+        self.assertTrue(Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]).any())
+        self.assertFalse(
+            Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]).any(
+                lambda x: x["value"] < 1
+            )
+        )
+        self.assertTrue(
+            Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]).any(
+                lambda x: x["value"] >= 1
+            )
+        )
 
     def test_contains(self):
         self.assertFalse(self.empty.contains(1))
@@ -364,15 +473,66 @@ def test_contains(self):
         self.assertTrue(self.complex.select(lambda x: x["value"]).contains(1))
 
     def test_intersect(self):
-        self.assertRaises(TypeError, self.empty.intersect, [])
-        self.assertListEqual(self.empty.intersect(self.empty).to_list(), [])
-        self.assertListEqual(self.empty.intersect(self.simple).to_list(), [])
-        self.assertListEqual(self.simple.intersect(self.simple).to_list(), _simple)
-        self.assertListEqual(self.simple.intersect(Enumerable([2])).to_list(), [2])
-        self.assertListEqual(self.simple.intersect(self.complex).to_list(), [])
-        self.assertListEqual(self.complex.intersect(self.complex).to_list(), _complex)
+        self.assertRaises(TypeError, Enumerable.empty().intersect, [])
+        self.assertListEqual([], Enumerable().intersect(Enumerable.empty()).to_list())
+        self.assertListEqual(
+            [], Enumerable.empty().intersect(Enumerable([1, 2, 3])).to_list()
+        )
+        self.assertListEqual(
+            [1, 2, 3], Enumerable([1, 2, 3]).intersect(Enumerable([1, 2, 3])).to_list()
+        )
+        self.assertListEqual(
+            [2], Enumerable([1, 2, 3]).intersect(Enumerable([2])).to_list()
+        )
+        self.assertListEqual(
+            [],
+            Enumerable([1, 2, 3])
+            .intersect(
+                Enumerable(
+                    [
+                        {"value": 1},
+                        {"value": 2},
+                        {"value": 3},
+                    ]
+                )
+            )
+            .to_list(),
+        )
         self.assertListEqual(
-            self.complex.intersect(Enumerable([{"value": 1}])).to_list(), [{"value": 1}]
+            [
+                {"value": 1},
+                {"value": 2},
+                {"value": 3},
+            ],
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            )
+            .intersect(
+                Enumerable(
+                    [
+                        {"value": 1},
+                        {"value": 2},
+                        {"value": 3},
+                    ]
+                )
+            )
+            .to_list(),
+        )
+        self.assertListEqual(
+            Enumerable(
+                [
+                    {"value": 1},
+                    {"value": 2},
+                    {"value": 3},
+                ]
+            )
+            .intersect(Enumerable([{"value": 1}]))
+            .to_list(),
+            [{"value": 1}],
         )
 
     def test_except(self):
@@ -464,20 +624,28 @@ def test_union(self):
         )
 
     def test_join(self):
-        self.assertRaises(TypeError, self.empty.join, [])
-        self.assertListEqual([], self.empty.join(self.empty).to_list())
-        self.assertListEqual([], self.empty.join(self.simple).to_list())
-        self.assertListEqual([], self.empty.join(self.complex).to_list())
+        self.assertRaises(TypeError, Enumerable.empty().join, [])
+        self.assertListEqual([], Enumerable().join(Enumerable()).to_list())
+        self.assertListEqual([], Enumerable().join(Enumerable([1, 2, 3])).to_list())
+        self.assertListEqual(
+            [],
+            Enumerable()
+            .join(Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]))
+            .to_list(),
+        )
 
-        self.assertListEqual([], self.simple.join(self.empty).to_list())
         self.assertListEqual(
             [(1, 1), (2, 2), (3, 3)],
-            self.simple.join(self.simple).order_by(lambda x: (x[0], x[1])).to_list(),
+            Enumerable([1, 2, 3])
+            .join(Enumerable([1, 2, 3]))
+            .order_by(lambda x: (x[0], x[1]))
+            .to_list(),
         )
         self.assertListEqual(
             [(1, 1), (2, 2), (3, 3)],
-            self.simple.join(
-                self.complex,
+            Enumerable([1, 2, 3])
+            .join(
+                Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]),
                 inner_key=lambda x: x["value"],
                 result_func=lambda x: (x[0], x[1]["value"]),
             )
@@ -487,40 +655,37 @@ def test_join(self):
 
         self.assertListEqual(
             [(1, 1), (2, 2), (3, 3)],
-            self.complex.join(
-                self.complex, result_func=lambda x: (x[0]["value"], x[1]["value"])
+            Enumerable([{"value": 1}, {"value": 2}, {"value": 3}])
+            .join(
+                Enumerable([{"value": 1}, {"value": 2}, {"value": 3}]),
+                result_func=lambda x: (x[0]["value"], x[1]["value"]),
             )
             .order_by(lambda x: (x[0], x[1]))
             .to_list(),
         )
 
     def test_group_join(self):
-        self.assertRaises(TypeError, self.empty.group_join, [])
-        self.assertListEqual([], self.empty.group_join(self.empty).to_list())
+        self.assertRaises(TypeError, Enumerable.empty().group_join, [])
+        self.assertListEqual([], Enumerable().group_join(Enumerable()).to_list())
 
-        simple_empty_gj = self.simple.group_join(self.empty)
-        self.assertEqual(3, simple_empty_gj.count())
-        self.assertListEqual(
-            [(1, []), (2, []), (3, [])],
-            simple_empty_gj.select(lambda g: (g[0], g[1].to_list())).to_list(),
-        )
+        simple_empty_gj = Enumerable([1, 2, 3]).group_join(self.empty)
+        self.assertListEqual([], simple_empty_gj.to_list())
 
-        complex_simple_gj = self.complex.group_join(
-            self.simple, outer_key=lambda x: x["value"]
-        )
+        complex_simple_gj = Enumerable(
+            [{"value": 1}, {"value": 2}, {"value": 3}]
+        ).group_join(Enumerable([1, 2, 3, 3]), outer_key=lambda x: x["value"])
         self.assertListEqual(
-            [({"value": 1}, [1]), ({"value": 2}, [2]), ({"value": 3}, [3])],
+            [({"value": 1}, [1]), ({"value": 2}, [2]), ({"value": 3}, [3, 3])],
             complex_simple_gj.select(lambda g: (g[0], g[1].to_list())).to_list(),
         )
 
-        simple_gj = self.simple.group_join(
+        simple_gj = Enumerable([1, 2, 3]).group_join(
             Enumerable([2, 3]),
             result_func=lambda x: {"number": x[0], "collection": x[1].to_list()},
         )
-        self.assertEqual(3, simple_gj.count())
+        self.assertEqual(2, simple_gj.count())
         self.assertListEqual(
             [
-                {"number": 1, "collection": []},
                 {"number": 2, "collection": [2]},
                 {"number": 3, "collection": [3]},
             ],
@@ -654,7 +819,7 @@ def test_reverse(self):
 
     def test_skip_last(self):
         test = Enumerable([1, 2, 3, 4, 5]).skip_last(2)
-        self.assertListEqual(test.to_list(), [1, 2, 3])
+        self.assertListEqual([1, 2, 3], test.to_list())
 
         test = Enumerable(["one", "two", "three", "four", "five"]).skip(1).skip_last(1)
         self.assertListEqual(test.to_list(), ["two", "three", "four"])
diff --git a/tests/test_issues.py b/tests/test_issues.py
index 07ce260..b865ff1 100644
--- a/tests/test_issues.py
+++ b/tests/test_issues.py
@@ -4,6 +4,7 @@
 from py_linq import Enumerable
 from tests import _empty, _simple, _complex
 from py_linq import exceptions
+import itertools
 
 
 class IssueTests(TestCase):
@@ -32,14 +33,6 @@ def my_iter():
             for i in range(10):
                 yield i
 
-        def low_iter():
-            for i in range(5):
-                yield i
-
-        def high_iter():
-            for k in range(5):
-                yield k + 5
-
         data = my_iter()
         a = Enumerable(data)
 
@@ -173,29 +166,34 @@ def append_to_list(lst, element):
         self.assertEqual("This line should be counted", result[0])
 
     def test_issue_47(self):
-        marks = Enumerable([(25,'a'), (49,'b'), (50,'c'), (80,'d'), (90,'e')])
+        marks = Enumerable([(25, "a"), (49, "b"), (50, "c"), (80, "d"), (90, "e")])
         passing = marks.where(lambda x: x[0] >= 50)
-        self.assertListEqual([(50, 'c'), (80, 'd'), (90, 'e')], passing.to_list())
-        omarks = Enumerable([(80,'eighty'),(49,'fortynine')])
-        join_result = omarks.join(passing, lambda o:o[0], lambda y:y[0], lambda result: result).to_list()
-        self.assertListEqual([((80, 'eighty'), (80, 'd'))], join_result)
+        self.assertListEqual([(50, "c"), (80, "d"), (90, "e")], passing.to_list())
+        omarks = Enumerable([(80, "eighty"), (49, "fortynine")])
+        join_result = omarks.join(
+            passing, lambda o: o[0], lambda y: y[0], lambda result: result
+        ).to_list()
+        self.assertListEqual([((80, "eighty"), (80, "d"))], join_result)
 
     def test_empty_join(self):
-        marks = Enumerable([(25,'a'), (49,'b'), (50,'c'), (80,'d'), (90,'e')])
+        marks = Enumerable([(25, "a"), (49, "b"), (50, "c"), (80, "d"), (90, "e")])
         passing = marks.where(lambda x: x[0] > 90)
         self.assertListEqual([], passing.to_list())
-        omarks = Enumerable([(80,'eighty'),(49,'fortynine')])
-        join_result = omarks.join(passing, lambda o:o[0], lambda y:y[0], lambda result: result).to_list()
+        omarks = Enumerable([(80, "eighty"), (49, "fortynine")])
+        join_result = omarks.join(
+            passing, lambda o: o[0], lambda y: y[0], lambda result: result
+        ).to_list()
         self.assertListEqual([], join_result)
 
     def test_issue_50(self):
         """
         Only single char for key value on group_by
         """
+
         class MyObject(object):
             def __init__(self, name):
                 self.field = name
-                
+
             def get_field(self):
                 return self.field
 
@@ -208,16 +206,89 @@ def test_issue_50_2(self):
         class MyObject(object):
             def __init__(self, name):
                 self.field = name
-                
+
             def get_field(self):
                 return self.field
 
         def get_field(item):
             return item.get_field()
 
-        test = Enumerable([MyObject("Bruce"), MyObject("Bruce"), MyObject("Fenske"), MyObject("Luke")])
-        group = test.group_by(key_names=['field_name'], key=get_field).order_by(lambda g: g.key.field_name)
+        test = Enumerable(
+            [MyObject("Bruce"), MyObject("Bruce"), MyObject("Fenske"), MyObject("Luke")]
+        )
+        group = test.group_by(key_names=["field_name"], key=get_field).order_by(
+            lambda g: g.key.field_name
+        )
         self.assertEqual("Bruce", group[0].key.field_name)
 
+    def test_issue_53(self):
+        test = Enumerable([{"name": "test", "value": "test"}])
+        test.add({"name": "test2", "value": "test2"})
+        self.assertListEqual(
+            [
+                {"name": "test", "value": "test"},
+                {"name": "test2", "value": "test2"},
+            ],
+            test.to_list(),
+        )
+        test.add(42)
+        self.assertListEqual(
+            [
+                {"name": "test", "value": "test"},
+                {"name": "test2", "value": "test2"},
+                42,
+            ],
+            test.to_list(),
+        )
 
+    def test_issue_53_2(self):
+        test = Enumerable()
+        self.assertListEqual([], test.to_list())
+        test.add({"name": "test", "value": "test"})
+        self.assertListEqual([{"name": "test", "value": "test"}], test.to_list())
+        test.add(42)
+        self.assertListEqual([{"name": "test", "value": "test"}, 42], test.to_list())
+
+    def test_issue_52(self):
+        def func(r):
+            return r
+
+        e1 = Enumerable([{"value": 1}, {"value": 2}, {"value": 3}, {"value": 0}])
+        e2 = Enumerable([1, 2, 3, 1, 2, 1])
+        res = e1.group_join(
+            e2,
+            outer_key=lambda x: x["value"],
+            inner_key=lambda y: y,
+            result_func=lambda r: func(r),
+        )
 
+        expected = [
+            ({"value": 1}, {"key": "{'id': 1}", "enumerable": "[1, 1, 1]"}),
+            ({"value": 2}, {"key": "{'id': 2}", "enumerable": "[2, 2]"}),
+            ({"value": 3}, {"key": "{'id': 3}", "enumerable": "[3]"}),
+        ]
+
+        self.assertEqual(len(expected), res.count())
+        self.assertEqual(3, next(res)[1].count())
+        self.assertEqual(2, next(res)[1].count())
+        self.assertEqual(1, next(res)[1].count())
+
+    def test_issue_52_2(self):
+
+        e1 = Enumerable([{"value": 1}, {"value": 2}, {"value": 3}, {"value": 0}])
+        e2 = Enumerable([1, 2, 3, 1, 2, 1])
+        res = e1.group_join(
+            e2,
+            outer_key=lambda x: x["value"],
+            inner_key=lambda y: y,
+            result_func=lambda r: (r[0], r[1].to_list()),
+        )
+
+        self.assertListEqual(
+            [
+                ({"value": 1}, [1, 1, 1]),
+                ({"value": 2}, [2, 2]),
+                ({"value": 3}, [3]),
+            ],
+            res.to_list(),
+        )
diff --git a/tests/test_performance.py b/tests/test_performance.py
index a49c21a..bb1d5c0 100644
--- a/tests/test_performance.py
+++ b/tests/test_performance.py
@@ -1,5 +1,6 @@
 import unittest
 import logging
+
 try:
     from time import clock as clock
 except ImportError: