pax_global_header00006660000000000000000000000064142747706530014531gustar00rootroot0000000000000052 comment=b85ea5a37026f2c19a82fb86568fb9de53de48c1 roundrobin-0.0.4/000077500000000000000000000000001427477065300137135ustar00rootroot00000000000000roundrobin-0.0.4/.gitignore000066400000000000000000000001061427477065300157000ustar00rootroot00000000000000*.pyc .DS_Store .pyre .pyre_configuration .watchmanconfig __pycache__ roundrobin-0.0.4/.travis.yml000066400000000000000000000002561427477065300160270ustar00rootroot00000000000000language: python python: - 2.7 - 3.4 - 3.5 - 3.6 - 3.7 - 3.8 - 3.9-dev - nightly - pypy - pypy3 install: - pip install . script: - python test.py -v roundrobin-0.0.4/LICENSE000066400000000000000000000020631427477065300147210ustar00rootroot00000000000000MIT License Copyright (c) 2020 Vyacheslav Linnik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. roundrobin-0.0.4/MANIFEST.in000066400000000000000000000000201427477065300154410ustar00rootroot00000000000000include LICENSE roundrobin-0.0.4/README.md000066400000000000000000000016511427477065300151750ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/linnik/roundrobin.svg?branch=master)](https://travis-ci.org/linnik/roundrobin) [![PyPI install](https://img.shields.io/badge/pip%20install-roundrobin-informational)](https://pypi.org/project/roundrobin/) ![versions](https://img.shields.io/pypi/pyversions/roundrobin.svg) # roundrobin This is rather small collection of round robin utilites ```python >>> import roundrobin >>> get_roundrobin = roundrobin.basic(["A", "B", "C"]) >>> ''.join([get_roundrobin() for _ in range(7)]) 'ABCABCA' >>> # weighted round-robin balancing algorithm as seen in LVS >>> get_weighted = roundrobin.weighted([("A", 5), ("B", 1), ("C", 1)]) >>> ''.join([get_weighted() for _ in range(7)]) 'AAAAABC' >>> # smooth weighted round-robin balancing algorithm as seen in Nginx >>> get_weighted_smooth = roundrobin.smooth([("A", 5), ("B", 1), ("C", 1)]) >>> ''.join([get_weighted_smooth() for _ in range(7)]) 'AABACAA' ``` roundrobin-0.0.4/roundrobin/000077500000000000000000000000001427477065300160745ustar00rootroot00000000000000roundrobin-0.0.4/roundrobin/__init__.py000066400000000000000000000002371427477065300202070ustar00rootroot00000000000000from roundrobin.basic_rr import basic from roundrobin.smooth_rr import smooth from roundrobin.weighted_rr import weighted __ALL__ = [basic, weighted, smooth] roundrobin-0.0.4/roundrobin/basic_rr.py000066400000000000000000000002301427477065300202250ustar00rootroot00000000000000from itertools import cycle def basic(dataset): iterator = cycle(dataset) def get_next(): return next(iterator) return get_next roundrobin-0.0.4/roundrobin/basic_rr.pyi000066400000000000000000000001271427477065300204030ustar00rootroot00000000000000from typing import Any, Callable def basic(dataset: [Any]) -> Callable[[], Any]: ... roundrobin-0.0.4/roundrobin/smooth_rr.py000066400000000000000000000021321427477065300204600ustar00rootroot00000000000000def smooth(dataset): dataset_length = len(dataset) dataset_extra_weights = [ItemWeight(*x) for x in dataset] def get_next(): if dataset_length == 0: return None if dataset_length == 1: return dataset[0][0] total_weight = 0 result = None for extra in dataset_extra_weights: extra.current_weight += extra.effective_weight total_weight += extra.effective_weight if extra.effective_weight < extra.weight: extra.effective_weight += 1 if not result or result.current_weight < extra.current_weight: result = extra if not result: # this should be unreachable, but check anyway raise RuntimeError result.current_weight -= total_weight return result.key return get_next class ItemWeight: __slots__ = ('key', 'weight', 'current_weight', 'effective_weight') def __init__(self, key, weight): self.key = key self.weight = weight self.current_weight = 0 self.effective_weight = weight roundrobin-0.0.4/roundrobin/smooth_rr.pyi000066400000000000000000000003511427477065300206320ustar00rootroot00000000000000from typing import Any, Callable, Tuple def smooth(dataset: [Tuple[Any, int]]) -> Callable[[], Any]: def get_next() -> Any: ... class ItemWeight: key: Any weight: int current_weight: int effective_weight: int roundrobin-0.0.4/roundrobin/weighted_rr.py000066400000000000000000000021371427477065300207540ustar00rootroot00000000000000try: from math import gcd except ImportError: from fractions import gcd # python2 workaround for python3 nonlocal keyword class Store: __slots__ = ('index', 'weight') def __init__(self, index, weight): self.index = index self.weight = weight def weighted(dataset): current = Store(index=-1, weight=0) dataset_length = len(dataset) dataset_max_weight = 0 dataset_gcd_weight = 0 for _, weight in dataset: if dataset_max_weight < weight: dataset_max_weight = weight dataset_gcd_weight = gcd(dataset_gcd_weight, weight) def get_next(): while True: current.index = (current.index + 1) % dataset_length if current.index == 0: current.weight = current.weight - dataset_gcd_weight if current.weight <= 0: current.weight = dataset_max_weight if current.weight == 0: return None if dataset[current.index][1] >= current.weight: return dataset[current.index][0] return get_next roundrobin-0.0.4/roundrobin/weighted_rr.pyi000066400000000000000000000002101427477065300211130ustar00rootroot00000000000000from typing import Any, Callable, Tuple def weighted(dataset: [Tuple[Any, int]]) -> Callable[[], Any]: def get_next() -> Any: ... roundrobin-0.0.4/setup.py000066400000000000000000000024331427477065300154270ustar00rootroot00000000000000import os import setuptools README_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "README.md") def setup(): readme_content = '' with open(README_PATH, "r") as fp: readme_content = fp.read() setuptools.setup( name="roundrobin", version="0.0.4", description="Collection of roundrobin utilities", long_description=readme_content, long_description_content_type="text/markdown", url="https://github.com/linnik/roundrobin", author="Vyacheslav Linnik", author_email="hello@slavalinnik.com", license="MIT", classifiers=[ "License :: OSI Approved :: MIT License", 'Programming Language :: Python', "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Operating System :: OS Independent', ], packages=setuptools.find_packages(), ) if __name__ == "__main__": setup() roundrobin-0.0.4/test.py000066400000000000000000000025421427477065300152470ustar00rootroot00000000000000import unittest import roundrobin class WeightCase(unittest.TestCase): def test_basic(self): data = ["A", "B", "C"] get_next = roundrobin.basic(data) result = ''.join([get_next() for _ in range(7)]) self.assertEqual(result, 'ABCABCA') def test_smooth(self): data = [("A", 5), ("B", 1), ("C", 1)] get_next = roundrobin.smooth(data) result = ''.join([get_next() for _ in range(7)]) self.assertEqual(result, 'AABACAA') def test_weighted_not_smooth(self): data = [("A", 5), ("B", 1), ("C", 1)] get_next = roundrobin.weighted(data) result = ''.join([get_next() for _ in range(7)]) self.assertEqual(result, 'AAAAABC') def test_weighted(self): data = [("A", 50), ("B", 35), ("C", 15)] get_next = roundrobin.weighted(data) cnt = {} for _ in range(100): key = get_next() cnt[key] = cnt.get(key, 0) + 1 self.assertTrue(cnt['A'] == 50) self.assertTrue(cnt['B'] == 35) self.assertTrue(cnt['C'] == 15) cnt = {} for _ in range(200): key = get_next() cnt[key] = cnt.get(key, 0) + 1 self.assertTrue(cnt['A'] == 100) self.assertTrue(cnt['B'] == 70) self.assertTrue(cnt['C'] == 30) if __name__ == "__main__": unittest.main()