roboherd 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of roboherd might be problematic. Click here for more details.
- roboherd/__main__.py +4 -2
- roboherd/annotations/__init__.py +22 -4
- roboherd/annotations/bovine.py +8 -8
- roboherd/annotations/common.py +3 -1
- roboherd/cow/__init__.py +51 -57
- roboherd/cow/const.py +6 -0
- roboherd/cow/profile.py +19 -5
- roboherd/cow/test_init.py +4 -3
- roboherd/cow/test_profile.py +35 -3
- roboherd/cow/types.py +17 -8
- roboherd/cow/util.py +4 -1
- roboherd/examples/json_echo.py +1 -1
- roboherd/examples/moocow.py +4 -2
- roboherd/examples/number.py +1 -1
- roboherd/examples/rooster.py +1 -1
- roboherd/examples/scarecrow.py +2 -1
- roboherd/herd/__init__.py +7 -7
- roboherd/herd/manager/__init__.py +4 -2
- roboherd/herd/manager/config.py +14 -7
- roboherd/herd/manager/load.py +15 -0
- roboherd/herd/manager/test_config.py +18 -2
- roboherd/herd/manager/test_load.py +19 -0
- roboherd/herd/manager/test_manager.py +1 -1
- roboherd/herd/processor.py +2 -2
- roboherd/herd/scheduler.py +1 -1
- roboherd/test_validators.py +10 -0
- roboherd/util.py +1 -51
- roboherd/validators.py +6 -0
- {roboherd-0.1.3.dist-info → roboherd-0.1.5.dist-info}/METADATA +2 -1
- roboherd-0.1.5.dist-info/RECORD +44 -0
- roboherd/test_util.py +0 -24
- roboherd-0.1.3.dist-info/RECORD +0 -40
- {roboherd-0.1.3.dist-info → roboherd-0.1.5.dist-info}/WHEEL +0 -0
- {roboherd-0.1.3.dist-info → roboherd-0.1.5.dist-info}/entry_points.txt +0 -0
roboherd/__main__.py
CHANGED
|
@@ -10,6 +10,7 @@ from roboherd.herd import RoboHerd
|
|
|
10
10
|
from roboherd.herd.manager import HerdManager
|
|
11
11
|
from roboherd.util import create_connection
|
|
12
12
|
from roboherd.register import register as run_register
|
|
13
|
+
from roboherd.validators import validators
|
|
13
14
|
|
|
14
15
|
logging.basicConfig(level=logging.INFO)
|
|
15
16
|
|
|
@@ -32,6 +33,7 @@ def main(ctx, connection_string, base_url, config_file):
|
|
|
32
33
|
settings = dynaconf.Dynaconf(
|
|
33
34
|
settings_files=[config_file],
|
|
34
35
|
envvar_prefix="ROBOHERD",
|
|
36
|
+
validators=validators,
|
|
35
37
|
)
|
|
36
38
|
ctx.ensure_object(dict)
|
|
37
39
|
|
|
@@ -41,12 +43,12 @@ def main(ctx, connection_string, base_url, config_file):
|
|
|
41
43
|
if connection_string:
|
|
42
44
|
ctx.obj["connection_string"] = connection_string
|
|
43
45
|
else:
|
|
44
|
-
ctx.obj["connection_string"] = settings.
|
|
46
|
+
ctx.obj["connection_string"] = settings.connection_string
|
|
45
47
|
|
|
46
48
|
if base_url:
|
|
47
49
|
ctx.obj["base_url"] = base_url
|
|
48
50
|
else:
|
|
49
|
-
ctx.obj["base_url"] = settings.
|
|
51
|
+
ctx.obj["base_url"] = settings.base_url
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
@main.command()
|
roboherd/annotations/__init__.py
CHANGED
|
@@ -5,11 +5,11 @@ from almabtrieb import Almabtrieb
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def get_raw(data: dict) -> dict:
|
|
8
|
-
return data.get("data").get("raw")
|
|
8
|
+
return data.get("data", {}).get("raw")
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def get_parsed(data: dict) -> dict:
|
|
12
|
-
result = data.get("data").get("parsed")
|
|
12
|
+
result = data.get("data", {}).get("parsed")
|
|
13
13
|
if result is None:
|
|
14
14
|
raise ValueError("No parsed data found")
|
|
15
15
|
return result
|
|
@@ -23,11 +23,17 @@ ParsedData = Annotated[dict, Depends(get_parsed)]
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def get_activity(parsed: ParsedData) -> dict:
|
|
26
|
-
|
|
26
|
+
result = parsed.get("activity")
|
|
27
|
+
if not result:
|
|
28
|
+
raise ValueError("No activity found")
|
|
29
|
+
return result
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
def get_embedded_object(parsed: ParsedData) -> dict:
|
|
30
|
-
|
|
33
|
+
result = parsed.get("embeddedObject")
|
|
34
|
+
if not result:
|
|
35
|
+
raise ValueError("No embedded object found")
|
|
36
|
+
return result
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
Activity = Annotated[dict, Depends(get_activity)]
|
|
@@ -37,6 +43,7 @@ EmbeddedObject = Annotated[dict, Depends(get_embedded_object)]
|
|
|
37
43
|
"""The embedded object in the activity as parsed by muck_out"""
|
|
38
44
|
|
|
39
45
|
Publisher = Callable[[dict], Awaitable[None]]
|
|
46
|
+
"""Type returned by the publishing functions"""
|
|
40
47
|
|
|
41
48
|
|
|
42
49
|
def construct_publish_object(connection: Almabtrieb, actor_id: str) -> Publisher:
|
|
@@ -46,5 +53,16 @@ def construct_publish_object(connection: Almabtrieb, actor_id: str) -> Publisher
|
|
|
46
53
|
return publish
|
|
47
54
|
|
|
48
55
|
|
|
56
|
+
def construct_publish_activity(connection: Almabtrieb, actor_id: str) -> Publisher:
|
|
57
|
+
async def publish(data: dict):
|
|
58
|
+
await connection.trigger("publish_activity", {"actor": actor_id, "data": data})
|
|
59
|
+
|
|
60
|
+
return publish
|
|
61
|
+
|
|
62
|
+
|
|
49
63
|
PublishObject = Annotated[Publisher, Depends(construct_publish_object)]
|
|
50
64
|
"""Allows one to publish an object as the actor. Assumes cattle_grid has the extension `simple_object_storage` or equivalent"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
PublishActivity = Annotated[Publisher, Depends(construct_publish_activity)]
|
|
68
|
+
"""Allows one to publish an activity as the actor. Assumes cattle_grid has the extension `simple_object_storage` or equivalent"""
|
roboherd/annotations/bovine.py
CHANGED
|
@@ -7,19 +7,19 @@ from .common import Profile
|
|
|
7
7
|
try:
|
|
8
8
|
from bovine.activitystreams import factories_for_actor_object
|
|
9
9
|
from bovine.activitystreams.activity_factory import (
|
|
10
|
-
ActivityFactory as BovineActivityFactory,
|
|
10
|
+
ActivityFactory as BovineActivityFactory, # type: ignore
|
|
11
11
|
)
|
|
12
12
|
from bovine.activitystreams.object_factory import (
|
|
13
|
-
ObjectFactory as BovineObjectFactory,
|
|
13
|
+
ObjectFactory as BovineObjectFactory, # type: ignore
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
def get_activity_factory(profile: Profile) -> BovineActivityFactory:
|
|
16
|
+
def get_activity_factory(profile: Profile) -> BovineActivityFactory: # type: ignore
|
|
17
17
|
activity_factory, _ = factories_for_actor_object(profile)
|
|
18
|
-
return activity_factory
|
|
18
|
+
return activity_factory # type: ignore
|
|
19
19
|
|
|
20
|
-
def get_object_factory(profile: Profile) -> BovineObjectFactory:
|
|
20
|
+
def get_object_factory(profile: Profile) -> BovineObjectFactory: # type: ignore
|
|
21
21
|
_, object_factory = factories_for_actor_object(profile)
|
|
22
|
-
return object_factory
|
|
22
|
+
return object_factory # type: ignore
|
|
23
23
|
|
|
24
24
|
except ImportError:
|
|
25
25
|
|
|
@@ -34,8 +34,8 @@ except ImportError:
|
|
|
34
34
|
raise ImportError("bovine not installed")
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
ActivityFactory = Annotated[BovineActivityFactory, Depends(get_activity_factory)]
|
|
37
|
+
ActivityFactory = Annotated[BovineActivityFactory, Depends(get_activity_factory)] # type: ignore
|
|
38
38
|
"""The activity factory of type [bovine.activitystreams.activity_factory.ActivityFactory][]"""
|
|
39
39
|
|
|
40
|
-
ObjectFactory = Annotated[BovineObjectFactory, Depends(get_object_factory)]
|
|
40
|
+
ObjectFactory = Annotated[BovineObjectFactory, Depends(get_object_factory)] # type: ignore
|
|
41
41
|
"""The object factory of type [bovine.activitystreams.object_factory.ObjectFactory][]"""
|
roboherd/annotations/common.py
CHANGED
|
@@ -5,7 +5,9 @@ from roboherd.cow import RoboCow
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def get_profile(cow: RoboCow) -> dict:
|
|
8
|
-
|
|
8
|
+
if cow.internals.profile is None:
|
|
9
|
+
raise ValueError("Cow has no profile")
|
|
10
|
+
return cow.internals.profile
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
Profile = Annotated[dict, Depends(get_profile)]
|
roboherd/cow/__init__.py
CHANGED
|
@@ -25,17 +25,8 @@ class CronEntry:
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@dataclass
|
|
28
|
-
class
|
|
29
|
-
|
|
30
|
-
metadata=dict(description="Information about the cow")
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
auto_follow: bool = field(
|
|
34
|
-
default=True,
|
|
35
|
-
metadata=dict(
|
|
36
|
-
description="""Whether to automatically accept follow requests"""
|
|
37
|
-
),
|
|
38
|
-
)
|
|
28
|
+
class RoboCowInternals:
|
|
29
|
+
"""Internal data for the cow"""
|
|
39
30
|
|
|
40
31
|
profile: dict | None = field(
|
|
41
32
|
default=None,
|
|
@@ -69,6 +60,27 @@ class RoboCow:
|
|
|
69
60
|
|
|
70
61
|
startup_routine: Callable | None = None
|
|
71
62
|
|
|
63
|
+
base_url: str | None = field(default=None)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class RoboCow:
|
|
68
|
+
information: Information = field(
|
|
69
|
+
metadata=dict(description="Information about the cow")
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
auto_follow: bool = field(
|
|
73
|
+
default=True,
|
|
74
|
+
metadata=dict(
|
|
75
|
+
description="""Whether to automatically accept follow requests"""
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
internals: RoboCowInternals = field(
|
|
80
|
+
default_factory=RoboCowInternals,
|
|
81
|
+
metadata=dict(description="Internal data for the cow"),
|
|
82
|
+
)
|
|
83
|
+
|
|
72
84
|
def action(self, action: str = "*", activity_type: str = "*"):
|
|
73
85
|
"""Adds a handler for an event. Use "*" as a wildcard.
|
|
74
86
|
|
|
@@ -90,15 +102,15 @@ class RoboCow:
|
|
|
90
102
|
|
|
91
103
|
def inner(func):
|
|
92
104
|
config.func = func
|
|
93
|
-
self.handlers.add_handler(config, func)
|
|
94
|
-
self.handler_configuration.append(config)
|
|
105
|
+
self.internals.handlers.add_handler(config, func)
|
|
106
|
+
self.internals.handler_configuration.append(config)
|
|
95
107
|
return func
|
|
96
108
|
|
|
97
109
|
return inner
|
|
98
110
|
|
|
99
111
|
def cron(self, crontab):
|
|
100
112
|
def inner(func):
|
|
101
|
-
self.cron_entries.append(CronEntry(crontab, func))
|
|
113
|
+
self.internals.cron_entries.append(CronEntry(crontab, func))
|
|
102
114
|
|
|
103
115
|
return func
|
|
104
116
|
|
|
@@ -119,7 +131,7 @@ class RoboCow:
|
|
|
119
131
|
action="incoming",
|
|
120
132
|
activity_type="*",
|
|
121
133
|
)
|
|
122
|
-
self.handlers.add_handler(config, func)
|
|
134
|
+
self.internals.handlers.add_handler(config, func)
|
|
123
135
|
return func
|
|
124
136
|
|
|
125
137
|
def incoming_create(self, func):
|
|
@@ -137,75 +149,57 @@ class RoboCow:
|
|
|
137
149
|
config = HandlerConfiguration(
|
|
138
150
|
action="incoming", activity_type="Create", func=func
|
|
139
151
|
)
|
|
140
|
-
self.handler_configuration.append(config)
|
|
141
|
-
self.handlers.add_handler(config, func)
|
|
152
|
+
self.internals.handler_configuration.append(config)
|
|
153
|
+
self.internals.handlers.add_handler(config, func)
|
|
142
154
|
return func
|
|
143
155
|
|
|
144
156
|
def startup(self, func):
|
|
145
157
|
"""Adds a startup routine to be run when the cow is started."""
|
|
146
158
|
|
|
147
|
-
self.startup_routine = func
|
|
159
|
+
self.internals.startup_routine = func
|
|
148
160
|
|
|
149
161
|
def needs_update(self):
|
|
150
162
|
"""Checks if the cow needs to be updated"""
|
|
151
|
-
if self.profile is None:
|
|
163
|
+
if self.internals.profile is None:
|
|
152
164
|
return True
|
|
153
165
|
|
|
154
|
-
if self.information.name != self.profile.get("name"):
|
|
166
|
+
if self.information.name != self.internals.profile.get("name"):
|
|
155
167
|
return True
|
|
156
168
|
|
|
157
|
-
if self.information.description != self.profile.get("summary"):
|
|
169
|
+
if self.information.description != self.internals.profile.get("summary"):
|
|
158
170
|
return True
|
|
159
171
|
|
|
160
172
|
return False
|
|
161
173
|
|
|
162
|
-
def update_data(self):
|
|
163
|
-
"""
|
|
164
|
-
Returns the update_actor message to send to cattle_grid
|
|
165
|
-
|
|
166
|
-
```pycon
|
|
167
|
-
>>> info = Information(handle="moocow", name="name", description="description")
|
|
168
|
-
>>> cow = RoboCow(information=info, actor_id="http://host.example/actor/1")
|
|
169
|
-
>>> cow.update_data()
|
|
170
|
-
{'actor': 'http://host.example/actor/1',
|
|
171
|
-
'profile': {'name': 'name',
|
|
172
|
-
'summary': 'description'},
|
|
173
|
-
'automaticallyUpdateFollowers': True}
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
"""
|
|
177
|
-
return {
|
|
178
|
-
"actor": self.actor_id,
|
|
179
|
-
"profile": {
|
|
180
|
-
"name": self.information.name,
|
|
181
|
-
"summary": self.information.description,
|
|
182
|
-
},
|
|
183
|
-
"automaticallyUpdateFollowers": self.auto_follow,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
174
|
async def run_startup(self, connection: Almabtrieb):
|
|
187
175
|
"""Runs when the cow is birthed"""
|
|
188
176
|
|
|
189
|
-
if self.profile is None:
|
|
190
|
-
|
|
191
|
-
|
|
177
|
+
if self.internals.profile is None:
|
|
178
|
+
if not self.internals.actor_id:
|
|
179
|
+
raise ValueError("Actor ID is not set")
|
|
180
|
+
result = await connection.fetch(
|
|
181
|
+
self.internals.actor_id, self.internals.actor_id
|
|
182
|
+
)
|
|
183
|
+
if not result.data:
|
|
184
|
+
raise ValueError("Could not retrieve profile")
|
|
185
|
+
self.internals.profile = result.data
|
|
192
186
|
|
|
193
|
-
if self.cron_entries:
|
|
187
|
+
if self.internals.cron_entries:
|
|
194
188
|
frequency = ", ".join(
|
|
195
|
-
get_description(entry.crontab) for entry in self.cron_entries
|
|
189
|
+
get_description(entry.crontab) for entry in self.internals.cron_entries
|
|
196
190
|
)
|
|
197
191
|
self.information.frequency = frequency
|
|
198
192
|
|
|
199
|
-
update = determine_profile_update(self.information, self.profile)
|
|
193
|
+
update = determine_profile_update(self.information, self.internals.profile)
|
|
200
194
|
|
|
201
195
|
if update:
|
|
202
196
|
logger.info("Updating profile for %s", self.information.handle)
|
|
203
197
|
|
|
204
198
|
await connection.trigger("update_actor", update)
|
|
205
199
|
|
|
206
|
-
if self.startup_routine:
|
|
207
|
-
await inject(self.startup_routine)(
|
|
208
|
-
cow=self,
|
|
209
|
-
connection=connection,
|
|
210
|
-
actor_id=self.actor_id,
|
|
211
|
-
)
|
|
200
|
+
if self.internals.startup_routine:
|
|
201
|
+
await inject(self.internals.startup_routine)(
|
|
202
|
+
cow=self, # type:ignore
|
|
203
|
+
connection=connection, # type:ignore
|
|
204
|
+
actor_id=self.internals.actor_id, # type:ignore
|
|
205
|
+
) # type:ignore
|
roboherd/cow/const.py
ADDED
roboherd/cow/profile.py
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
from urllib.parse import urlparse
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from bovine.activitystreams.utils import as_list
|
|
5
|
+
except ImportError:
|
|
6
|
+
|
|
7
|
+
def as_list(value):
|
|
8
|
+
if isinstance(value, list):
|
|
9
|
+
return value
|
|
10
|
+
|
|
11
|
+
return [value]
|
|
12
|
+
|
|
3
13
|
|
|
4
14
|
from .types import Information
|
|
5
15
|
|
|
@@ -14,6 +24,9 @@ def profile_part_needs_update(information: Information, profile: dict) -> bool:
|
|
|
14
24
|
if information.type != profile.get("type"):
|
|
15
25
|
return True
|
|
16
26
|
|
|
27
|
+
if information.icon != profile.get("icon"):
|
|
28
|
+
return True
|
|
29
|
+
|
|
17
30
|
return False
|
|
18
31
|
|
|
19
32
|
|
|
@@ -27,7 +40,7 @@ def key_index_from_attachment(attachments: list[dict], key: str) -> int | None:
|
|
|
27
40
|
|
|
28
41
|
|
|
29
42
|
def determine_action_for_key_and_value(
|
|
30
|
-
attachments: list[dict], key: str, value: str
|
|
43
|
+
attachments: list[dict], key: str, value: str | None
|
|
31
44
|
) -> dict | None:
|
|
32
45
|
idx = key_index_from_attachment(attachments, key)
|
|
33
46
|
if idx is None:
|
|
@@ -78,19 +91,19 @@ def determine_actions(information: Information, profile: dict) -> list[dict] | N
|
|
|
78
91
|
"identifier": "acct:"
|
|
79
92
|
+ information.handle
|
|
80
93
|
+ "@"
|
|
81
|
-
+ urlparse(profile.get("id")).netloc,
|
|
94
|
+
+ str(urlparse(profile.get("id")).netloc),
|
|
82
95
|
"primary": True,
|
|
83
96
|
}
|
|
84
97
|
)
|
|
85
98
|
|
|
86
|
-
actions =
|
|
99
|
+
actions = [x for x in actions if x is not None]
|
|
87
100
|
|
|
88
101
|
if len(actions) == 0:
|
|
89
102
|
return None
|
|
90
103
|
return actions
|
|
91
104
|
|
|
92
105
|
|
|
93
|
-
def determine_profile_update(information: Information, profile: dict) -> dict:
|
|
106
|
+
def determine_profile_update(information: Information, profile: dict) -> dict | None:
|
|
94
107
|
"""Returns the update for the profile"""
|
|
95
108
|
|
|
96
109
|
update = {"actor": profile.get("id")}
|
|
@@ -100,6 +113,7 @@ def determine_profile_update(information: Information, profile: dict) -> dict:
|
|
|
100
113
|
"type": information.type,
|
|
101
114
|
"name": information.name,
|
|
102
115
|
"summary": information.description,
|
|
116
|
+
"icon": information.icon,
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
actions = determine_actions(information, profile)
|
roboherd/cow/test_init.py
CHANGED
|
@@ -27,7 +27,8 @@ def test_needs_update(name, summary, profile, expected):
|
|
|
27
27
|
name=name,
|
|
28
28
|
description=summary,
|
|
29
29
|
)
|
|
30
|
-
cow = RoboCow(information=info
|
|
30
|
+
cow = RoboCow(information=info)
|
|
31
|
+
cow.internals.profile = profile
|
|
31
32
|
|
|
32
33
|
assert cow.needs_update() == expected
|
|
33
34
|
|
|
@@ -40,13 +41,13 @@ def test_cron():
|
|
|
40
41
|
async def test_func():
|
|
41
42
|
pass
|
|
42
43
|
|
|
43
|
-
assert len(cow.cron_entries) == 1
|
|
44
|
+
assert len(cow.internals.cron_entries) == 1
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
async def test_startup():
|
|
47
48
|
info = Information(handle="testcow")
|
|
48
49
|
cow = RoboCow(information=info)
|
|
49
|
-
cow.profile = {"id": "http://host.test/actor/cow"}
|
|
50
|
+
cow.internals.profile = {"id": "http://host.test/actor/cow"}
|
|
50
51
|
mock = AsyncMock()
|
|
51
52
|
|
|
52
53
|
cow.startup(mock)
|
roboherd/cow/test_profile.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from .types import Information, MetaInformation
|
|
4
|
-
|
|
4
|
+
from .const import default_icon
|
|
5
5
|
from .profile import determine_profile_update
|
|
6
6
|
|
|
7
7
|
|
|
@@ -14,6 +14,7 @@ from .profile import determine_profile_update
|
|
|
14
14
|
],
|
|
15
15
|
)
|
|
16
16
|
def test_determine_profile_update_no_update(info_params, profile):
|
|
17
|
+
profile["icon"] = default_icon
|
|
17
18
|
info = Information(**info_params)
|
|
18
19
|
|
|
19
20
|
assert determine_profile_update(info, profile) is None
|
|
@@ -27,13 +28,22 @@ def test_determine_profile_update():
|
|
|
27
28
|
|
|
28
29
|
assert result == {
|
|
29
30
|
"actor": "http://host.test/actor/1",
|
|
30
|
-
"profile": {
|
|
31
|
+
"profile": {
|
|
32
|
+
"type": "Service",
|
|
33
|
+
"name": "name",
|
|
34
|
+
"summary": "description",
|
|
35
|
+
"icon": default_icon,
|
|
36
|
+
},
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
def test_determine_profile_update_author():
|
|
35
41
|
info = Information(meta_information=MetaInformation(author="acct:author@host.test"))
|
|
36
|
-
profile = {
|
|
42
|
+
profile = {
|
|
43
|
+
"id": "http://host.test/actor/1",
|
|
44
|
+
"type": "Service",
|
|
45
|
+
"icon": default_icon,
|
|
46
|
+
}
|
|
37
47
|
|
|
38
48
|
result = determine_profile_update(info, profile)
|
|
39
49
|
|
|
@@ -47,3 +57,25 @@ def test_determine_profile_update_author():
|
|
|
47
57
|
}
|
|
48
58
|
],
|
|
49
59
|
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_profile_update_for_new_preferredUsername():
|
|
63
|
+
info = Information(handle="handle")
|
|
64
|
+
profile = {
|
|
65
|
+
"id": "http://host.test/actor/1",
|
|
66
|
+
"type": "Service",
|
|
67
|
+
"icon": default_icon,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
result = determine_profile_update(info, profile)
|
|
71
|
+
|
|
72
|
+
assert result == {
|
|
73
|
+
"actor": "http://host.test/actor/1",
|
|
74
|
+
"actions": [
|
|
75
|
+
{
|
|
76
|
+
"action": "add_identifier",
|
|
77
|
+
"identifier": "acct:handle@host.test",
|
|
78
|
+
"primary": True,
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
}
|
roboherd/cow/types.py
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
from pydantic import BaseModel, Field
|
|
2
2
|
|
|
3
|
+
from .const import default_icon
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
class MetaInformation(BaseModel):
|
|
5
7
|
"""Meta Information about the bot. This includes
|
|
6
8
|
information such as the author and the source repository"""
|
|
7
9
|
|
|
8
10
|
source: str | None = Field(
|
|
9
|
-
None,
|
|
11
|
+
default=None,
|
|
10
12
|
examples=["https://forge.example/repo"],
|
|
11
13
|
description="The source repository",
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
author: str | None = Field(
|
|
15
|
-
None,
|
|
17
|
+
default=None,
|
|
16
18
|
examples=["acct:author@domain.example"],
|
|
17
19
|
description="The author, often a Fediverse handle",
|
|
18
20
|
)
|
|
@@ -22,23 +24,25 @@ class Information(BaseModel):
|
|
|
22
24
|
"""Information about the cow"""
|
|
23
25
|
|
|
24
26
|
type: str = Field(
|
|
25
|
-
|
|
27
|
+
default="Service",
|
|
28
|
+
examples=["Service"],
|
|
29
|
+
description="ActivityPub type of the actor.",
|
|
26
30
|
)
|
|
27
31
|
|
|
28
32
|
handle: str | None = Field(
|
|
29
|
-
None,
|
|
33
|
+
default=None,
|
|
30
34
|
examples=["moocow"],
|
|
31
35
|
description="Used as the handle in `acct:handle@domain.example`",
|
|
32
36
|
)
|
|
33
37
|
|
|
34
38
|
name: str | None = Field(
|
|
35
|
-
None,
|
|
39
|
+
default=None,
|
|
36
40
|
examples=["The mooing cow 🐮"],
|
|
37
41
|
description="The display name of the cow",
|
|
38
42
|
)
|
|
39
43
|
|
|
40
44
|
description: str | None = Field(
|
|
41
|
-
None,
|
|
45
|
+
default=None,
|
|
42
46
|
examples=[
|
|
43
47
|
"I'm a cow that moos.",
|
|
44
48
|
"""<p>An example bot to illustrate Roboherd</p><p>For more information on RoboHerd, see <a href="https://codeberg.org/bovine/roboherd">its repository</a>.</p>""",
|
|
@@ -46,13 +50,18 @@ class Information(BaseModel):
|
|
|
46
50
|
description="The description of the cow, used as summary of the actor",
|
|
47
51
|
)
|
|
48
52
|
|
|
53
|
+
icon: dict = Field(
|
|
54
|
+
default=default_icon,
|
|
55
|
+
description="The profile image",
|
|
56
|
+
)
|
|
57
|
+
|
|
49
58
|
frequency: str | None = Field(
|
|
50
|
-
None,
|
|
59
|
+
default=None,
|
|
51
60
|
examples=["daily"],
|
|
52
61
|
description="Frequency of posting. Is set automatically if cron expressions are used.",
|
|
53
62
|
)
|
|
54
63
|
|
|
55
64
|
meta_information: MetaInformation = Field(
|
|
56
|
-
MetaInformation(),
|
|
65
|
+
default=MetaInformation(),
|
|
57
66
|
description="Meta information about the cow, such as the source repository",
|
|
58
67
|
)
|
roboherd/cow/util.py
CHANGED
roboherd/examples/json_echo.py
CHANGED
roboherd/examples/moocow.py
CHANGED
|
@@ -21,7 +21,9 @@ See <a href="https://codeberg.org/helge/roboherd">codeberg.org</a>.""",
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@moocow.incoming_create
|
|
24
|
-
async def on_incoming_create(
|
|
24
|
+
async def on_incoming_create(
|
|
25
|
+
obj: EmbeddedObject, publisher: PublishObject, actor_id: str
|
|
26
|
+
):
|
|
25
27
|
recipient = obj.get("attributedTo")
|
|
26
28
|
|
|
27
29
|
logger.info("Replying to %s", recipient)
|
|
@@ -29,7 +31,7 @@ async def on_incoming_create(obj: EmbeddedObject, publisher: PublishObject):
|
|
|
29
31
|
obj = {
|
|
30
32
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
31
33
|
"type": "Note",
|
|
32
|
-
"attributedTo":
|
|
34
|
+
"attributedTo": actor_id,
|
|
33
35
|
"to": [recipient],
|
|
34
36
|
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
|
|
35
37
|
"content": "moo",
|
roboherd/examples/number.py
CHANGED
|
@@ -51,7 +51,7 @@ async def post_number(
|
|
|
51
51
|
):
|
|
52
52
|
number = random.randint(0, 1000)
|
|
53
53
|
|
|
54
|
-
note = factory.note(content=f"Number: {number}").as_public().build()
|
|
54
|
+
note = factory.note(content=f"Number: {number}").as_public().build() # type: ignore
|
|
55
55
|
await publisher(note)
|
|
56
56
|
|
|
57
57
|
handle = "even" if number % 2 == 0 else "odd"
|
roboherd/examples/rooster.py
CHANGED
|
@@ -18,5 +18,5 @@ bot = RoboCow(
|
|
|
18
18
|
@bot.cron("42 * * * *")
|
|
19
19
|
async def crow(publisher: PublishObject, object_factory: ObjectFactory):
|
|
20
20
|
await publisher(
|
|
21
|
-
object_factory.note(content="cock-a-doodle-doo").as_public().build()
|
|
21
|
+
object_factory.note(content="cock-a-doodle-doo").as_public().build() # type: ignore
|
|
22
22
|
)
|
roboherd/examples/scarecrow.py
CHANGED
|
@@ -18,4 +18,5 @@ bot = RoboCow(
|
|
|
18
18
|
|
|
19
19
|
@bot.startup
|
|
20
20
|
async def startup(publish_object: PublishObject, object_factory: ObjectFactory):
|
|
21
|
-
|
|
21
|
+
note = object_factory.note(content="Booo! 🐦").as_public().build() # type: ignore
|
|
22
|
+
await publish_object(note)
|
roboherd/herd/__init__.py
CHANGED
|
@@ -29,6 +29,9 @@ class RoboHerd:
|
|
|
29
29
|
await self.process(connection)
|
|
30
30
|
|
|
31
31
|
async def startup(self, connection: Almabtrieb):
|
|
32
|
+
if not connection.information:
|
|
33
|
+
raise Exception("Could not get information from server")
|
|
34
|
+
|
|
32
35
|
self.cows = self.manager.existing_cows(connection.information.actors)
|
|
33
36
|
|
|
34
37
|
cows_to_create = self.manager.cows_to_create(connection.information.actors)
|
|
@@ -38,12 +41,12 @@ class RoboHerd:
|
|
|
38
41
|
cow = cow_config.load()
|
|
39
42
|
result = await connection.create_actor(
|
|
40
43
|
name=f"{self.manager.prefix}{cow_config.name}",
|
|
41
|
-
base_url=self.base_url,
|
|
44
|
+
base_url=cow.internals.base_url or self.base_url,
|
|
42
45
|
preferred_username=cow.information.handle,
|
|
43
46
|
profile={"type": "Service"},
|
|
44
47
|
automatically_accept_followers=True,
|
|
45
48
|
)
|
|
46
|
-
cow.actor_id = result.get("id")
|
|
49
|
+
cow.internals.actor_id = result.get("id")
|
|
47
50
|
|
|
48
51
|
self.cows.append(cow)
|
|
49
52
|
|
|
@@ -60,9 +63,6 @@ class RoboHerd:
|
|
|
60
63
|
scheduler = HerdScheduler(self.cron_entries(), connection)
|
|
61
64
|
scheduler.create_task(tg)
|
|
62
65
|
|
|
63
|
-
def introduce(self, cow: RoboCow):
|
|
64
|
-
self.manager.add_to_herd(cow)
|
|
65
|
-
|
|
66
66
|
def validate(self, connection):
|
|
67
67
|
result = connection.information
|
|
68
68
|
|
|
@@ -81,7 +81,7 @@ class RoboHerd:
|
|
|
81
81
|
|
|
82
82
|
result = []
|
|
83
83
|
for cow in self.cows:
|
|
84
|
-
for cron_entry in cow.cron_entries:
|
|
84
|
+
for cron_entry in cow.internals.cron_entries:
|
|
85
85
|
result.append((cow, cron_entry))
|
|
86
86
|
|
|
87
87
|
return result
|
|
@@ -89,6 +89,6 @@ class RoboHerd:
|
|
|
89
89
|
def incoming_handlers(self) -> List[RoboCow]:
|
|
90
90
|
result = []
|
|
91
91
|
for cow in self.cows:
|
|
92
|
-
if cow.handlers.has_handlers:
|
|
92
|
+
if cow.internals.handlers.has_handlers:
|
|
93
93
|
result.append(cow)
|
|
94
94
|
return result
|
|
@@ -30,7 +30,7 @@ class HerdManager:
|
|
|
30
30
|
cow_config = self.herd_config.for_name(cow_name)
|
|
31
31
|
if cow_config:
|
|
32
32
|
cow = cow_config.load()
|
|
33
|
-
cow.actor_id = info.id
|
|
33
|
+
cow.internals.actor_id = info.id
|
|
34
34
|
existing_cows.append(cow)
|
|
35
35
|
|
|
36
36
|
return existing_cows
|
|
@@ -43,4 +43,6 @@ class HerdManager:
|
|
|
43
43
|
}
|
|
44
44
|
names_to_create = self.herd_config.names - existing_names
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
cows = {self.herd_config.for_name(name) for name in names_to_create}
|
|
47
|
+
|
|
48
|
+
return {cow for cow in cows if cow}
|
roboherd/herd/manager/config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
|
|
3
3
|
from roboherd.cow import RoboCow
|
|
4
|
-
from
|
|
4
|
+
from .load import load_cow
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@dataclass
|
|
@@ -9,19 +9,26 @@ class CowConfig:
|
|
|
9
9
|
name: str = field(metadata={"description": "Name of the cow, must be unique"})
|
|
10
10
|
module: str
|
|
11
11
|
attribute: str
|
|
12
|
+
config: dict
|
|
12
13
|
|
|
13
14
|
@staticmethod
|
|
14
15
|
def from_name_and_dict(name, cow: dict) -> "CowConfig":
|
|
15
16
|
module, attribute = cow["bot"].split(":")
|
|
16
17
|
|
|
17
|
-
return CowConfig(
|
|
18
|
-
name=name,
|
|
19
|
-
module=module,
|
|
20
|
-
attribute=attribute,
|
|
21
|
-
)
|
|
18
|
+
return CowConfig(name=name, module=module, attribute=attribute, config=cow)
|
|
22
19
|
|
|
23
20
|
def load(self) -> RoboCow:
|
|
24
|
-
|
|
21
|
+
cow = load_cow(self.module, self.attribute)
|
|
22
|
+
|
|
23
|
+
if "name" in self.config:
|
|
24
|
+
cow.information.name = self.config["name"]
|
|
25
|
+
if "handle" in self.config:
|
|
26
|
+
cow.information.handle = self.config["handle"]
|
|
27
|
+
|
|
28
|
+
if "base_url" in self.config:
|
|
29
|
+
cow.internals.base_url = self.config["base_url"]
|
|
30
|
+
|
|
31
|
+
return cow
|
|
25
32
|
|
|
26
33
|
def __hash__(self):
|
|
27
34
|
return hash(self.name)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import importlib
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
from roboherd.cow import RoboCow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_cow(module_name: str, attribute: str) -> RoboCow:
|
|
9
|
+
"""Loads a cow from module name and attribute"""
|
|
10
|
+
module = import_module(module_name)
|
|
11
|
+
importlib.reload(module)
|
|
12
|
+
|
|
13
|
+
cow = getattr(module, attribute)
|
|
14
|
+
|
|
15
|
+
return copy.deepcopy(cow)
|
|
@@ -21,7 +21,7 @@ def test_config():
|
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
}
|
|
24
|
-
)
|
|
24
|
+
) # type: ignore
|
|
25
25
|
return config
|
|
26
26
|
|
|
27
27
|
|
|
@@ -38,13 +38,29 @@ def test_from_name_and_dict():
|
|
|
38
38
|
assert cow.attribute == "attribute"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def test_from_name_and_dict_with_new_name():
|
|
42
|
+
name = "cow"
|
|
43
|
+
value = {
|
|
44
|
+
"bot": "roboherd.examples.moocow:moocow",
|
|
45
|
+
"handle": "new_handle",
|
|
46
|
+
"name": "new name",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
config = CowConfig.from_name_and_dict(name, value)
|
|
50
|
+
|
|
51
|
+
cow = config.load()
|
|
52
|
+
|
|
53
|
+
assert cow.information.name == "new name"
|
|
54
|
+
assert cow.information.handle == "new_handle"
|
|
55
|
+
|
|
56
|
+
|
|
41
57
|
def test_load_config(test_config):
|
|
42
58
|
herd = HerdConfig.from_settings(test_config)
|
|
43
59
|
|
|
44
60
|
assert len(herd.cows) == 2
|
|
45
61
|
|
|
46
62
|
moocow = herd.for_name("moocow")
|
|
47
|
-
|
|
63
|
+
assert moocow
|
|
48
64
|
assert moocow.name == "moocow"
|
|
49
65
|
|
|
50
66
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from roboherd.cow import RoboCow
|
|
2
|
+
|
|
3
|
+
from .load import load_cow
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_load_cow():
|
|
7
|
+
cow = load_cow("roboherd.examples.moocow", "moocow")
|
|
8
|
+
assert isinstance(cow, RoboCow)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_load_cow_can_overwrite_variables():
|
|
12
|
+
one = load_cow("roboherd.examples.moocow", "moocow")
|
|
13
|
+
|
|
14
|
+
one.information.name = "A cow eating watermelons"
|
|
15
|
+
|
|
16
|
+
two = load_cow("roboherd.examples.moocow", "moocow")
|
|
17
|
+
|
|
18
|
+
assert two.information.name == "The mooing cow 🐮"
|
|
19
|
+
assert one.information.name == "A cow eating watermelons"
|
roboherd/herd/processor.py
CHANGED
|
@@ -26,7 +26,7 @@ class HerdProcessor:
|
|
|
26
26
|
async def process_incoming(self, connection):
|
|
27
27
|
actor_id_to_cow_map = {}
|
|
28
28
|
for cow in self.incoming_handlers:
|
|
29
|
-
actor_id_to_cow_map[cow.actor_id] = cow
|
|
29
|
+
actor_id_to_cow_map[cow.internals.actor_id] = cow
|
|
30
30
|
|
|
31
31
|
async for msg in connection.incoming():
|
|
32
32
|
actor_id = msg["actor"]
|
|
@@ -34,6 +34,6 @@ class HerdProcessor:
|
|
|
34
34
|
cow = actor_id_to_cow_map.get(actor_id)
|
|
35
35
|
logger.info(cow)
|
|
36
36
|
if cow:
|
|
37
|
-
await cow.handlers.handle(
|
|
37
|
+
await cow.internals.handlers.handle(
|
|
38
38
|
msg, "incoming", connection, actor_id, cow=cow
|
|
39
39
|
)
|
roboherd/herd/scheduler.py
CHANGED
roboherd/util.py
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import logging
|
|
3
|
-
import copy
|
|
4
|
-
import importlib
|
|
5
|
-
from importlib import import_module
|
|
6
|
-
from urllib.parse import parse_qs, urlparse
|
|
7
3
|
|
|
4
|
+
from urllib.parse import urlparse
|
|
8
5
|
from almabtrieb import Almabtrieb
|
|
9
6
|
|
|
10
|
-
from roboherd.cow import RoboCow
|
|
11
7
|
|
|
12
8
|
logger = logging.getLogger(__name__)
|
|
13
9
|
|
|
@@ -47,52 +43,6 @@ def parse_connection_string(connection_string: str) -> dict:
|
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
|
|
50
|
-
def load_cow(module_name: str, attribute: str) -> RoboCow:
|
|
51
|
-
"""Loads a cow from module name and attribute"""
|
|
52
|
-
module = import_module(module_name)
|
|
53
|
-
importlib.reload(module)
|
|
54
|
-
|
|
55
|
-
cow = getattr(module, attribute)
|
|
56
|
-
|
|
57
|
-
return copy.deepcopy(cow)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def import_cow(name: str) -> RoboCow:
|
|
61
|
-
"""Imports a cow from a string of the form
|
|
62
|
-
`module.name:attribute`. Here attribute should
|
|
63
|
-
be of type [roboherd.cow.RoboCow][roboherd.cow.RoboCow].
|
|
64
|
-
|
|
65
|
-
```pycon
|
|
66
|
-
>>> cow = import_cow("roboherd.examples.moocow:moocow")
|
|
67
|
-
>>> cow.information.handle
|
|
68
|
-
'moocow'
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
"""
|
|
72
|
-
try:
|
|
73
|
-
query = None
|
|
74
|
-
module_name, attribute = name.split(":")
|
|
75
|
-
if "?" in attribute:
|
|
76
|
-
attribute, query = attribute.split("?")
|
|
77
|
-
|
|
78
|
-
cow = load_cow(module_name, attribute)
|
|
79
|
-
|
|
80
|
-
if query:
|
|
81
|
-
parsed_query = parse_qs(query)
|
|
82
|
-
handle = parsed_query.get("handle", [None])[0]
|
|
83
|
-
if handle:
|
|
84
|
-
cow.information.handle = handle
|
|
85
|
-
|
|
86
|
-
return cow
|
|
87
|
-
|
|
88
|
-
except Exception as e:
|
|
89
|
-
logger.error("Failed to import cow with name: %s", name)
|
|
90
|
-
logger.error("names should have the form module:attribute")
|
|
91
|
-
logger.exception(e)
|
|
92
|
-
|
|
93
|
-
raise ImportError("Failed to load module")
|
|
94
|
-
|
|
95
|
-
|
|
96
46
|
def create_connection(ctx):
|
|
97
47
|
connection_string = ctx.obj["connection_string"]
|
|
98
48
|
base_url = ctx.obj["base_url"]
|
roboherd/validators.py
ADDED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: roboherd
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A Fediverse bot framework
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: aiohttp>=3.11.12
|
|
6
7
|
Requires-Dist: almabtrieb[mqtt]>=0.1.0a1
|
|
7
8
|
Requires-Dist: apscheduler>=3.11.0
|
|
8
9
|
Requires-Dist: click>=8.1.8
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
roboherd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
roboherd/__main__.py,sha256=cY4f-39QCDhStJ4gDuuSz46RDFE_AKnu08sTCpQEEV4,2933
|
|
3
|
+
roboherd/register.py,sha256=Gqa5aT2supVJMj7z21btMYRma3_WW0oK5gjZftr048s,976
|
|
4
|
+
roboherd/test_validators.py,sha256=UiFgJkutmXBLjGpdLP2IBYCEf75quwnRHX7Z51G8Xqo,222
|
|
5
|
+
roboherd/util.py,sha256=nrFRtyfvMQ8oXoGDYxmCAXcIN14dWBAPGv0cI_B_TP0,1710
|
|
6
|
+
roboherd/validators.py,sha256=2mc43ZGwFazp4f3B9J4RxZCU4Y_ErSNotib8MnYVtmY,140
|
|
7
|
+
roboherd/annotations/__init__.py,sha256=UhDSVQ4cj2iwIr5qn6dZMAyyY-A9KMJHnqhyVYsRYww,2095
|
|
8
|
+
roboherd/annotations/bovine.py,sha256=qXkliFVT63BfGn820Bopwd7O41ofP7VbNomxyit4zBg,1494
|
|
9
|
+
roboherd/annotations/common.py,sha256=DN1jt1P79pU7XnmiocwTQTTQnn3vJlSSkP8kiHGAbfs,334
|
|
10
|
+
roboherd/cow/__init__.py,sha256=MlMvjFbodwZf6vpKlPfIpSn5SHu9IugacSH5ylbySTY,5826
|
|
11
|
+
roboherd/cow/const.py,sha256=fj5nUJUIlcpr1AU2Ur55ToR7iVmYv4UnfdxiQwguv-k,166
|
|
12
|
+
roboherd/cow/handlers.py,sha256=k5Tc1M--wqmZ2EZvzIfID4dp8XE0rN18puMTKkNVjjE,1491
|
|
13
|
+
roboherd/cow/profile.py,sha256=ldeYq50lh97dl50QjOZiGopEbXtCoEsE5QJoXB5XUYU,3330
|
|
14
|
+
roboherd/cow/test_handlers.py,sha256=SwrZZcU_BD2BpJUFg18mFEsyUqu7N81-MkjIaGv7whQ,1673
|
|
15
|
+
roboherd/cow/test_init.py,sha256=bnK8IIbbeS1Y7vJaFg5HzCL6UoZeCl6mo_SAQqJAgQY,1409
|
|
16
|
+
roboherd/cow/test_profile.py,sha256=f7HE0iVgbpuNv6znEqi-4l1o_8UZ9ufQpjSVP7Xf1wc,2160
|
|
17
|
+
roboherd/cow/test_util.py,sha256=8FLRtVdSMmIo6NSpCpB9mS0PwOCpGgUeDAA1q6Gv0P4,430
|
|
18
|
+
roboherd/cow/types.py,sha256=TGXTcPuND7xMly3xFXZyIR7UE3XWyF_MLRuBHWKoFEE,1925
|
|
19
|
+
roboherd/cow/util.py,sha256=ASQn7AnSl5lskECqCyqOKn5BYC8yOces4FK3uNV8010,534
|
|
20
|
+
roboherd/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
roboherd/examples/dev_null.py,sha256=6SZ9wlcawSBqhdq6Zv4xPXH5eKH2g1VaRwv3hSCDucE,308
|
|
22
|
+
roboherd/examples/json_echo.py,sha256=EFqNwaSKs0hTQjKZVatjn6i3O7rQff2GTHnEfpL1ha0,1370
|
|
23
|
+
roboherd/examples/meta.py,sha256=tFhHe-DJn2J13Mm22f-gFm5I8_3uQwLU4mAkrK2xzEM,170
|
|
24
|
+
roboherd/examples/moocow.py,sha256=DMPuR8XBpRRHKXFZM-2_VLYLWx8Iz4OdcE0QTVtYSFs,1097
|
|
25
|
+
roboherd/examples/number.py,sha256=Sj9aYfdtzANUfWVJnoX73zEvDNVcdrr-STZDSwNRHlI,2003
|
|
26
|
+
roboherd/examples/rooster.py,sha256=5_rPGLQTLv1s0cyE6l7WXLum-QLpQZ2p63YBkfrxKFw,596
|
|
27
|
+
roboherd/examples/scarecrow.py,sha256=KGZP0e5WHkCNSnGSlW3BeymcRZ4PqaWjAs_q5IC-1fE,649
|
|
28
|
+
roboherd/herd/__init__.py,sha256=6jWgYnuJOpXtYS03mdjEgWd0zXv5kj9w9d7K6UsCPbc,3068
|
|
29
|
+
roboherd/herd/builder.py,sha256=MSVPRF0Jsxure9kdyCoYJHQ7nYilGAD0_uQaGQ-rQyE,619
|
|
30
|
+
roboherd/herd/processor.py,sha256=ncXsYfuTRTT_0-K453COF_oAiGBJN0u5eP8NoeZmWik,1042
|
|
31
|
+
roboherd/herd/scheduler.py,sha256=pbWxOo9pnjAoAJhbvaczmSghkp6Y8Zp2HgXZ5zoOnDA,1276
|
|
32
|
+
roboherd/herd/test_herd.py,sha256=sQkzGCWdFveLklhaOJUybtl7odO-QOSDdd-_gan1py8,845
|
|
33
|
+
roboherd/herd/test_scheduler.py,sha256=wLisqRMSl734P_rjbqMNH5WTQKepwihgr7ZC32nEj80,424
|
|
34
|
+
roboherd/herd/types.py,sha256=_EidQbglm0jpsKX1EsL6U2qm_J5wCPhwUi6Avac22Ow,210
|
|
35
|
+
roboherd/herd/manager/__init__.py,sha256=NqOJsp1CdAobjARJGmzvU1ceTW7j2bt0FdRbpM8iFUw,1464
|
|
36
|
+
roboherd/herd/manager/config.py,sha256=VDuxB7GMoZdgSM2i1iG1o-IjtyScsSnO--6PEM8tx9U,1591
|
|
37
|
+
roboherd/herd/manager/load.py,sha256=BoeBID2UGP--sIKwITABQkQv2lMc9Y8pyp7_nleu2bw,351
|
|
38
|
+
roboherd/herd/manager/test_config.py,sha256=CnoqQ_exrpQdBWVoGxSns3HpccqgmhH9mNJIPTCKKAs,1665
|
|
39
|
+
roboherd/herd/manager/test_load.py,sha256=zyu5LIChMfTnxu_tYK63-bSOHYn1K1zUlbDY5DkE3GY,514
|
|
40
|
+
roboherd/herd/manager/test_manager.py,sha256=9pSMaH7zmN-zagYCIBpQcV3Q0sBT7XZSCvsmLVC0rOI,1047
|
|
41
|
+
roboherd-0.1.5.dist-info/METADATA,sha256=TU89GQjIVilPWX8Lc_It5tNYKCPuoI0MXaWdN-t25CY,830
|
|
42
|
+
roboherd-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
43
|
+
roboherd-0.1.5.dist-info/entry_points.txt,sha256=WebdVUmh8Ot-FupKJY6Du8LuFbmezt9yoy2UICqV3bE,52
|
|
44
|
+
roboherd-0.1.5.dist-info/RECORD,,
|
roboherd/test_util.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from roboherd.cow import RoboCow
|
|
4
|
-
|
|
5
|
-
from .util import import_cow
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_import_cow():
|
|
9
|
-
cow = import_cow("roboherd.examples.moocow:moocow")
|
|
10
|
-
|
|
11
|
-
assert isinstance(cow, RoboCow)
|
|
12
|
-
assert cow.information.handle == "moocow"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_import_cow_failed():
|
|
16
|
-
with pytest.raises(ImportError):
|
|
17
|
-
import_cow("robocow:nocow")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_import_cow_with_handle():
|
|
21
|
-
cow = import_cow("roboherd.examples.moocow:moocow?handle=horse")
|
|
22
|
-
|
|
23
|
-
assert isinstance(cow, RoboCow)
|
|
24
|
-
assert cow.information.handle == "horse"
|
roboherd-0.1.3.dist-info/RECORD
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
roboherd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
roboherd/__main__.py,sha256=y2Ge8zajHn4rB4SlunLzmH5MNMCxklrhTaDdMbujjq8,2873
|
|
3
|
-
roboherd/register.py,sha256=Gqa5aT2supVJMj7z21btMYRma3_WW0oK5gjZftr048s,976
|
|
4
|
-
roboherd/test_util.py,sha256=Yor_YgXtvK3WnZu6jr7DvNtTLdR9YWHCA5QufHo_w2s,535
|
|
5
|
-
roboherd/util.py,sha256=23X_-CiJoW6xd36YhTzIkXoS3y_eN5ILJDCC0GnBhm0,3065
|
|
6
|
-
roboherd/annotations/__init__.py,sha256=hdW1HypuV6duCYxEhR2a9eidgcrC5-oAM-hHyzVh10E,1425
|
|
7
|
-
roboherd/annotations/bovine.py,sha256=3LnS19pJ6Z1pSB50IaTxDnvaPlU_4qp_lFyQjAtJ5P0,1366
|
|
8
|
-
roboherd/annotations/common.py,sha256=xr8FanMMPA1DDnCdguY4DLG9DbqmziaZxNR4-ww2ewk,239
|
|
9
|
-
roboherd/cow/__init__.py,sha256=j6a1i_kAndo71Ufoy1bZr1FC9othg-cz-6x9z5yQ75E,5866
|
|
10
|
-
roboherd/cow/handlers.py,sha256=k5Tc1M--wqmZ2EZvzIfID4dp8XE0rN18puMTKkNVjjE,1491
|
|
11
|
-
roboherd/cow/profile.py,sha256=XEFU8wJYVQxrlbAhUWZSDhfxBn1IXm_6f8YBlpHvUjM,3060
|
|
12
|
-
roboherd/cow/test_handlers.py,sha256=SwrZZcU_BD2BpJUFg18mFEsyUqu7N81-MkjIaGv7whQ,1673
|
|
13
|
-
roboherd/cow/test_init.py,sha256=Te-4Z8lPpMlSv99OkzPLMtB607NSOfdRD3EB-6_3n1A,1370
|
|
14
|
-
roboherd/cow/test_profile.py,sha256=edWKVL8VmOb8XtE1OQmwDLuw4mcDtxMKBfA9Kbd71qU,1423
|
|
15
|
-
roboherd/cow/test_util.py,sha256=8FLRtVdSMmIo6NSpCpB9mS0PwOCpGgUeDAA1q6Gv0P4,430
|
|
16
|
-
roboherd/cow/types.py,sha256=GtDnvpiH0w-Ux6BkhjNOgRdZ9YOITPB16BZd51MzKfA,1709
|
|
17
|
-
roboherd/cow/util.py,sha256=oBytl3cOhYgZCCnihM3wkMxG8xQZUlohVSxCaaHDsYc,493
|
|
18
|
-
roboherd/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
roboherd/examples/dev_null.py,sha256=6SZ9wlcawSBqhdq6Zv4xPXH5eKH2g1VaRwv3hSCDucE,308
|
|
20
|
-
roboherd/examples/json_echo.py,sha256=vOwbCX7apXh87RNFdwDWZXaIpJuJO5Tcsck_9qjRpqw,1354
|
|
21
|
-
roboherd/examples/meta.py,sha256=tFhHe-DJn2J13Mm22f-gFm5I8_3uQwLU4mAkrK2xzEM,170
|
|
22
|
-
roboherd/examples/moocow.py,sha256=OkceeaRqkjR31Nue8anHtL_DMqd7finLzs-gUaKsVBo,1083
|
|
23
|
-
roboherd/examples/number.py,sha256=U71mcmLVqtOkl6vkudHKlJmxmLVCI11q1ekQOoQfTag,1987
|
|
24
|
-
roboherd/examples/rooster.py,sha256=505MzfGM6iqQtffncLOA9Oj5aDeep-_oWS7yEzJbh4o,580
|
|
25
|
-
roboherd/examples/scarecrow.py,sha256=o-54QN63LmxfufslmCDvx1ilWQ7KVMsUBtl1tPMbkEs,617
|
|
26
|
-
roboherd/herd/__init__.py,sha256=DUvzuyB-JWD-awhNzseoMvluugeD02PZF-hJrGVNtJc,2981
|
|
27
|
-
roboherd/herd/builder.py,sha256=MSVPRF0Jsxure9kdyCoYJHQ7nYilGAD0_uQaGQ-rQyE,619
|
|
28
|
-
roboherd/herd/processor.py,sha256=NkROTAPs6ZoYW_0TSDnNkAfos4cTnaFgwtW3wOFSgQY,1022
|
|
29
|
-
roboherd/herd/scheduler.py,sha256=fkR-74bFZ73DmlJje_dQSytxnFFLV5hCa067mXdwvXs,1266
|
|
30
|
-
roboherd/herd/test_herd.py,sha256=sQkzGCWdFveLklhaOJUybtl7odO-QOSDdd-_gan1py8,845
|
|
31
|
-
roboherd/herd/test_scheduler.py,sha256=wLisqRMSl734P_rjbqMNH5WTQKepwihgr7ZC32nEj80,424
|
|
32
|
-
roboherd/herd/types.py,sha256=_EidQbglm0jpsKX1EsL6U2qm_J5wCPhwUi6Avac22Ow,210
|
|
33
|
-
roboherd/herd/manager/__init__.py,sha256=n4QFJXQafHhOJZyDO-mAAWE_hoXcE9vYwFGOPqFOiJM,1409
|
|
34
|
-
roboherd/herd/manager/config.py,sha256=qfcED9PfzKzDCWaYHefYj8AImcsOFs5daGpJwUrXlV4,1313
|
|
35
|
-
roboherd/herd/manager/test_config.py,sha256=cSf6cqFFr5Hbubuc_tDHyDPbVnsKbRg12WUONn6RxIc,1260
|
|
36
|
-
roboherd/herd/manager/test_manager.py,sha256=U6xLOry1K74hd_l8ZtO56v8h1TLNn-UYYfsZJuxuCDA,1031
|
|
37
|
-
roboherd-0.1.3.dist-info/METADATA,sha256=dsglqXLDAENTeakmj_vUQu8_jWg7JkRKjdOwMeuKm3s,798
|
|
38
|
-
roboherd-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
39
|
-
roboherd-0.1.3.dist-info/entry_points.txt,sha256=WebdVUmh8Ot-FupKJY6Du8LuFbmezt9yoy2UICqV3bE,52
|
|
40
|
-
roboherd-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|