Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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".
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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."
Expand Down Expand Up @@ -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
5 changes: 4 additions & 1 deletion scripts/auto/anchor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion scripts/auto/joiner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion scripts/manual/anchor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion scripts/manual/joiner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion scripts/manual/step.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
10 changes: 6 additions & 4 deletions src/chordnet/__init__.py
Original file line number Diff line number Diff line change
@@ -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']
File renamed without changes.
4 changes: 2 additions & 2 deletions src/chordnet/node.py → src/chordnet/_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions src/chordnet/chordnet.py
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions tests/test_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.