diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b44c3c7..0bda39c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ open issue you want to work on, please leave a comment on it - assigned issues are spearheaded by the assignee, if they need help they will coordinate on the issue. -## Development +## Development Environment This project uses `uv` and `pre-commit`. to install `uv`, you can use homebrew: ```sh @@ -19,3 +19,10 @@ Once `uv` is installed, clone this repo, then run the following: uv sync --locked --all-extras --dev # this installs all dev dependencies, without upgrading any. uv run pre-commit install # sets up pre-commit ``` + +## Organization +This project uses a `src` layout. all tests are in the `tests` directory, and can be +run with `pytest` from root. the `scripts` directory contains useful scripts for +debugging nodes, including a set of "auto" scripts (which runs normally and periodically +prints the finger table), and "manual" scripts (which include a step function, to manually +run one iteration of the daemon and print the result". diff --git a/README.md b/README.md index 5e5dc9c..0c97578 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,25 @@ Fall 2024. `uv add chordnet` -## Development -See `CONTRIBUTING.md`. ## Usage to stay consistent with the language from the original paper, we recommend -importing this package as `ring`: +naming your chordnet attribute `ring`: ```python -from chordnet import Node as ring +from chordnet import ChordNet + +ring = new ChordNet(...) +ring.create() +# or ring.join(...) +#... +ring.leave() ``` This fits with the concept of "joining" an existing ring network, or creating a -new one, (`ring.join(...)`, `ring.create()`. -Examples follow this practice. +new one. Examples follow this practice. + +## Development +See `CONTRIBUTING.md`. + +## Security +If you discover a security issue, **please do not open a public issue**. +See `SECURITY.md` for the full policy/reporting instructions. diff --git a/pyproject.toml b/pyproject.toml index 50cd929..ef7ee2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "chordnet" -version = "1.1.0" +version = "2.0.0" license = "MIT" license-files = ["LICENSE"] description = "Implementation of the chord peer-to-peer networking protocol, introduced by Stoica et al." @@ -119,13 +119,11 @@ convention = "google" python_version = "3.10" warn_return_any = true warn_unused_configs = true +mypy_path = "src" files = ["src", "tests", "scripts"] exclude = [ '(^|.*/)__pycache__/', # matches __pycache__ at any depth '(^|.*/).ruff_cache/', '(^|.*/).mypy_cache/', + '(^|.*/)dist/', ] - -[[tool.mypy.overrides]] -module = ["bpython"] -ignore_missing_imports = true diff --git a/scripts/auto/anchor.py b/scripts/auto/anchor.py index 700a16b..0be1c48 100644 --- a/scripts/auto/anchor.py +++ b/scripts/auto/anchor.py @@ -2,8 +2,11 @@ import sys import IPython +from loguru import logger -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode + +logger.enable("chordnet") def main() -> None: diff --git a/scripts/auto/joiner.py b/scripts/auto/joiner.py index 262e49f..a74cff2 100644 --- a/scripts/auto/joiner.py +++ b/scripts/auto/joiner.py @@ -2,8 +2,11 @@ import sys import IPython +from loguru import logger -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode + +logger.enable("chordnet") def main() -> None: diff --git a/scripts/manual/anchor.py b/scripts/manual/anchor.py index 87ce1f7..e4e8179 100644 --- a/scripts/manual/anchor.py +++ b/scripts/manual/anchor.py @@ -4,7 +4,7 @@ import IPython from step import step #type: ignore -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode def main() -> None: diff --git a/scripts/manual/joiner.py b/scripts/manual/joiner.py index df4b82d..48218d7 100644 --- a/scripts/manual/joiner.py +++ b/scripts/manual/joiner.py @@ -4,7 +4,7 @@ import IPython from step import step #type: ignore -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode def main() -> None: diff --git a/scripts/manual/step.py b/scripts/manual/step.py index f716674..af127ee 100644 --- a/scripts/manual/step.py +++ b/scripts/manual/step.py @@ -1,6 +1,6 @@ """step.py: helper for manual scripts.""" -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode def step(node: ChordNode) -> None: diff --git a/src/chordnet/__init__.py b/src/chordnet/__init__.py index 502e714..d561a6c 100644 --- a/src/chordnet/__init__.py +++ b/src/chordnet/__init__.py @@ -1,6 +1,8 @@ """init.py: defines importable classes.""" -from .address import Address -from .net import _Net -from .node import Node +from loguru import logger -__all__=['Node', 'Address', '_Net'] +from .chordnet import ChordNet + +logger.disable("chordnet") + +__all__=['ChordNet'] diff --git a/src/chordnet/net.py b/src/chordnet/_net.py similarity index 100% rename from src/chordnet/net.py rename to src/chordnet/_net.py diff --git a/src/chordnet/node.py b/src/chordnet/_node.py similarity index 99% rename from src/chordnet/node.py rename to src/chordnet/_node.py index 3248428..ec68000 100644 --- a/src/chordnet/node.py +++ b/src/chordnet/_node.py @@ -4,11 +4,11 @@ from loguru import logger as log +from ._net import _Net from .address import Address -from .net import _Net callback_t = Callable[[str, list[str]], str | Address | None] -class Node: +class _Node: """Implements a Chord distributed hash table node. This is meant to run on a host and handle any chord-related diff --git a/src/chordnet/chordnet.py b/src/chordnet/chordnet.py new file mode 100644 index 0000000..0c0b37e --- /dev/null +++ b/src/chordnet/chordnet.py @@ -0,0 +1,51 @@ +"""chordnet.py: chordnet api.""" +from ._node import _Node + + +class ChordNet: + """Interface for interacting with Chord networks.""" + + _node: _Node + + def __init__(self, + ip: str, + port: int, + interval:float=1.0, + ) -> None: + """Initializes a new Chord node. + + Args: + ip: IP address for the node. + port: Port number to listen on. + daemon: whether to run the daemon. + interval: daemon interval. + debug: whether to print node state after every daemon run. + """ + self._node = _Node(ip, port, interval=interval) + + + def create(self) -> None: + """Create a new ChordNet network (a new "ring"). + + This creates a new network with one node (this one). + """ + self._node.create() + + def join(self, known_ip: str, known_port: int) -> None: + """Joins an existing ChordNet network (an existing ring). + + An existing chordnet can be joined through any node already on the ring. + + Args: + known_ip (str): IP address of an existing node in the Chord ring. + known_port (int): Port number of the existing node. + """ + self._node.join(known_ip, known_port) + + def leave(self) -> None: + """Leave the current network. + + Allows for a graceful shutdown of this node. Should be called before + program exit, but the network can recover if this does not happen. + """ + pass diff --git a/tests/test_net.py b/tests/test_net.py index 51281e4..02f0aea 100644 --- a/tests/test_net.py +++ b/tests/test_net.py @@ -9,10 +9,11 @@ from unittest.mock import MagicMock, Mock, patch import pytest +from loguru import logger -from chordnet import ( - _Net, -) +from chordnet._net import _Net + +logger.enable("chordnet") def test_net_initialization() -> None: diff --git a/tests/test_node.py b/tests/test_node.py index 3b3c444..1c43b2c 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -8,10 +8,12 @@ from unittest.mock import MagicMock, patch import pytest +from loguru import logger -from chordnet import Address -from chordnet import Node as ChordNode +from chordnet._node import _Node as ChordNode +from chordnet.address import Address +logger.enable("chordnet") # Global test variables ip: str = "1.2.3.4" port: int = 5 diff --git a/uv.lock b/uv.lock index b352d66..96f0004 100644 --- a/uv.lock +++ b/uv.lock @@ -26,7 +26,7 @@ wheels = [ [[package]] name = "chordnet" -version = "1.1.0" +version = "2.0.0" source = { editable = "." } dependencies = [ { name = "loguru" },