Skip to content

Commit be3cb57

Browse files
authored
Merge pull request #1 from BlockScience/dev
1.0.0-beta.13
2 parents 4f80915 + 1960c9f commit be3cb57

File tree

7 files changed

+151
-77
lines changed

7 files changed

+151
-77
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ rid-lib
22
__pycache__
33
*.json
44
venv
5+
.env
56
prototypes
67
.vscode
78
dist/

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "koi-net"
7-
version = "1.0.0-beta.12"
7+
version = "1.0.0-beta.13"
88
description = "Implementation of KOI-net protocol in Python"
99
authors = [
1010
{name = "Luke Miller", email = "[email protected]"}

src/koi_net/config.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import os
2+
from typing import TypeVar
3+
from ruamel.yaml import YAML
4+
from koi_net.protocol.node import NodeProfile
5+
from rid_lib.types import KoiNetNode
6+
from pydantic import BaseModel, Field, PrivateAttr
7+
from dotenv import load_dotenv
8+
9+
10+
class ServerConfig(BaseModel):
11+
host: str | None = "127.0.0.1"
12+
port: int | None = 8000
13+
path: str | None = None
14+
15+
@property
16+
def url(self):
17+
return f"http://{self.host}:{self.port}{self.path or ''}"
18+
19+
class KoiNetConfig(BaseModel):
20+
node_name: str
21+
node_rid: KoiNetNode | None = None
22+
node_profile: NodeProfile
23+
24+
cache_directory_path: str | None = ".rid_cache"
25+
event_queues_path: str | None = "event_queues.json"
26+
27+
first_contact: str | None = None
28+
29+
class EnvConfig(BaseModel):
30+
def __init__(self, **kwargs):
31+
super().__init__(**kwargs)
32+
load_dotenv()
33+
34+
def __getattribute__(self, name):
35+
value = super().__getattribute__(name)
36+
if name in type(self).model_fields:
37+
env_val = os.getenv(value)
38+
if env_val is None:
39+
raise ValueError(f"Required environment variable {value} not set")
40+
return env_val
41+
return value
42+
43+
class Config(BaseModel):
44+
server: ServerConfig | None = Field(default_factory=ServerConfig)
45+
koi_net: KoiNetConfig
46+
_file_path: str = PrivateAttr(default="config.yaml")
47+
_file_content: str | None = PrivateAttr(default=None)
48+
49+
@classmethod
50+
def load_from_yaml(
51+
cls,
52+
file_path: str | None = None,
53+
generate_missing: bool = True
54+
):
55+
yaml = YAML()
56+
57+
try:
58+
with open(file_path, "r") as f:
59+
file_content = f.read()
60+
config_data = yaml.load(file_content)
61+
config = cls.model_validate(config_data)
62+
config._file_content = file_content
63+
64+
except FileNotFoundError:
65+
config = cls()
66+
67+
config._file_path = file_path
68+
69+
if generate_missing:
70+
config.koi_net.node_rid = (
71+
config.koi_net.node_rid or KoiNetNode.generate(config.koi_net.node_name)
72+
)
73+
config.koi_net.node_profile.base_url = (
74+
config.koi_net.node_profile.base_url or config.server.url
75+
)
76+
77+
config.save_to_yaml()
78+
79+
return config
80+
81+
def save_to_yaml(self):
82+
yaml = YAML()
83+
84+
with open(self._file_path, "w") as f:
85+
try:
86+
config_data = self.model_dump(mode="json")
87+
yaml.dump(config_data, f)
88+
except Exception as e:
89+
if self._file_content:
90+
f.seek(0)
91+
f.truncate()
92+
f.write(self._file_content)
93+
raise e
94+
95+
ConfigType = TypeVar("ConfigType", bound=Config)

src/koi_net/core.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,49 @@
11
import logging
2+
from typing import Generic
23
import httpx
34
from rid_lib.ext import Cache, Bundle
45
from .network import NetworkInterface
56
from .processor import ProcessorInterface
67
from .processor import default_handlers
78
from .processor.handler import KnowledgeHandler
89
from .identity import NodeIdentity
9-
from .protocol.node import NodeProfile
1010
from .protocol.event import Event, EventType
11+
from .config import ConfigType
1112

1213
logger = logging.getLogger(__name__)
1314

1415

15-
class NodeInterface:
16+
17+
class NodeInterface(Generic[ConfigType]):
18+
config: ConfigType
1619
cache: Cache
1720
identity: NodeIdentity
1821
network: NetworkInterface
1922
processor: ProcessorInterface
20-
first_contact: str
23+
2124
use_kobj_processor_thread: bool
2225

2326
def __init__(
2427
self,
25-
name: str,
26-
profile: NodeProfile,
27-
identity_file_path: str = "identity.json",
28-
event_queues_file_path: str = "event_queues.json",
29-
cache_directory_path: str = "rid_cache",
28+
config: ConfigType,
3029
use_kobj_processor_thread: bool = False,
31-
first_contact: str | None = None,
30+
3231
handlers: list[KnowledgeHandler] | None = None,
32+
3333
cache: Cache | None = None,
3434
network: NetworkInterface | None = None,
3535
processor: ProcessorInterface | None = None
3636
):
37-
self.cache = cache or Cache(cache_directory_path)
37+
self.config: ConfigType = config
38+
self.cache = cache or Cache(
39+
self.config.koi_net.cache_directory_path)
40+
3841
self.identity = NodeIdentity(
39-
name=name,
40-
profile=profile,
41-
cache=self.cache,
42-
file_path=identity_file_path
43-
)
44-
self.first_contact = first_contact
42+
config=self.config,
43+
cache=self.cache)
44+
4545
self.network = network or NetworkInterface(
46-
file_path=event_queues_file_path,
47-
first_contact=self.first_contact,
46+
config=self.config,
4847
cache=self.cache,
4948
identity=self.identity
5049
)
@@ -58,13 +57,14 @@ def __init__(
5857

5958
self.use_kobj_processor_thread = use_kobj_processor_thread
6059
self.processor = processor or ProcessorInterface(
60+
config=self.config,
6161
cache=self.cache,
6262
network=self.network,
6363
identity=self.identity,
6464
use_kobj_processor_thread=self.use_kobj_processor_thread,
6565
default_handlers=handlers
6666
)
67-
67+
6868
def start(self) -> None:
6969
"""Starts a node, call this method first.
7070
@@ -91,8 +91,8 @@ def start(self) -> None:
9191
self.processor.flush_kobj_queue()
9292
logger.debug("Done")
9393

94-
if not self.network.graph.get_neighbors() and self.first_contact:
95-
logger.debug(f"I don't have any neighbors, reaching out to first contact {self.first_contact}")
94+
if not self.network.graph.get_neighbors() and self.config.koi_net.first_contact:
95+
logger.debug(f"I don't have any neighbors, reaching out to first contact {self.config.koi_net.first_contact}")
9696

9797
events = [
9898
Event.from_rid(EventType.FORGET, self.identity.rid),
@@ -101,7 +101,7 @@ def start(self) -> None:
101101

102102
try:
103103
self.network.request_handler.broadcast_events(
104-
url=self.first_contact,
104+
url=self.config.koi_net.first_contact,
105105
events=events
106106
)
107107

src/koi_net/identity.py

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,41 @@
11
import logging
2-
from pydantic import BaseModel
32
from rid_lib.ext.bundle import Bundle
43
from rid_lib.ext.cache import Cache
54
from rid_lib.types.koi_net_node import KoiNetNode
5+
6+
from .config import Config
67
from .protocol.node import NodeProfile
78

89
logger = logging.getLogger(__name__)
910

10-
11-
class NodeIdentityModel(BaseModel):
12-
rid: KoiNetNode
13-
profile: NodeProfile
1411

1512
class NodeIdentity:
1613
"""Represents a node's identity (RID, profile, bundle)."""
1714

18-
_identity: NodeIdentityModel
19-
file_path: str
15+
config: Config
2016
cache: Cache
2117

2218
def __init__(
23-
self,
24-
name: str,
25-
profile: NodeProfile,
26-
cache: Cache,
27-
file_path: str = "identity.json"
19+
self,
20+
config: Config,
21+
cache: Cache
2822
):
2923
"""Initializes node identity from a name and profile.
3024
3125
Attempts to read identity from storage. If it doesn't already exist, a new RID is generated from the provided name, and that RID and profile are written to storage. Changes to the name or profile will update the stored identity.
3226
3327
WARNING: If the name is changed, the RID will be overwritten which will have consequences for the rest of the network.
3428
"""
29+
self.config = config
3530
self.cache = cache
36-
self.file_path = file_path
37-
38-
self._identity = None
39-
try:
40-
with open(file_path, "r") as f:
41-
self._identity = NodeIdentityModel.model_validate_json(f.read())
42-
43-
except FileNotFoundError:
44-
pass
45-
46-
if self._identity:
47-
if self._identity.rid.name != name:
48-
logger.warning("Node name changed which will change this node's RID, if you really want to do this manually delete the identity JSON file")
49-
if self._identity.profile != profile:
50-
self._identity.profile = profile
51-
else:
52-
self._identity = NodeIdentityModel(
53-
rid=KoiNetNode.generate(name),
54-
profile=profile,
55-
)
56-
57-
with open(file_path, "w") as f:
58-
f.write(self._identity.model_dump_json(indent=2))
5931

6032
@property
6133
def rid(self) -> KoiNetNode:
62-
return self._identity.rid
34+
return self.config.koi_net.node_rid
6335

6436
@property
6537
def profile(self) -> NodeProfile:
66-
return self._identity.profile
38+
return self.config.koi_net.node_profile
6739

6840
@property
6941
def bundle(self) -> Bundle:

0 commit comments

Comments
 (0)