roboherd 0.1.8__tar.gz → 0.1.10__tar.gz

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.

Files changed (66) hide show
  1. {roboherd-0.1.8 → roboherd-0.1.10}/.woodpecker/create_release.yml +1 -2
  2. {roboherd-0.1.8 → roboherd-0.1.10}/.woodpecker/publish_docker.yml +1 -1
  3. {roboherd-0.1.8 → roboherd-0.1.10}/CHANGES.md +10 -0
  4. roboherd-0.1.10/PKG-INFO +78 -0
  5. roboherd-0.1.10/README.md +57 -0
  6. {roboherd-0.1.8 → roboherd-0.1.10}/docs/index.md +9 -0
  7. {roboherd-0.1.8 → roboherd-0.1.10}/mkdocs.yml +2 -0
  8. {roboherd-0.1.8 → roboherd-0.1.10}/pyproject.toml +2 -2
  9. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/__main__.py +26 -5
  10. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/rooster.py +1 -0
  11. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/__init__.py +23 -6
  12. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/__init__.py +1 -2
  13. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/processor.py +4 -1
  14. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/scheduler.py +19 -4
  15. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/test_herd.py +2 -2
  16. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/register.py +4 -2
  17. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/util.py +2 -2
  18. roboherd-0.1.10/uv.lock +1925 -0
  19. roboherd-0.1.8/PKG-INFO +0 -27
  20. roboherd-0.1.8/README.md +0 -6
  21. roboherd-0.1.8/uv.lock +0 -1663
  22. {roboherd-0.1.8 → roboherd-0.1.10}/.gitignore +0 -0
  23. {roboherd-0.1.8 → roboherd-0.1.10}/.woodpecker/publish_pypi.yml +0 -0
  24. {roboherd-0.1.8 → roboherd-0.1.10}/.woodpecker/test.yml +0 -0
  25. {roboherd-0.1.8 → roboherd-0.1.10}/.woodpecker/website.yml +0 -0
  26. {roboherd-0.1.8 → roboherd-0.1.10}/docs/annotations.md +0 -0
  27. {roboherd-0.1.8 → roboherd-0.1.10}/docs/assets/bull-horns.png +0 -0
  28. {roboherd-0.1.8 → roboherd-0.1.10}/docs/assets/mastodon.png +0 -0
  29. {roboherd-0.1.8 → roboherd-0.1.10}/docs/cli.md +0 -0
  30. {roboherd-0.1.8 → roboherd-0.1.10}/docs/cow.md +0 -0
  31. {roboherd-0.1.8 → roboherd-0.1.10}/docs/herd.md +0 -0
  32. {roboherd-0.1.8 → roboherd-0.1.10}/docs/main.md +0 -0
  33. {roboherd-0.1.8 → roboherd-0.1.10}/docs/util.md +0 -0
  34. {roboherd-0.1.8 → roboherd-0.1.10}/resources/docker/Dockerfile +0 -0
  35. {roboherd-0.1.8 → roboherd-0.1.10}/resources/docker/build.sh +0 -0
  36. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/__init__.py +0 -0
  37. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/annotations/__init__.py +0 -0
  38. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/annotations/bovine.py +0 -0
  39. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/annotations/common.py +0 -0
  40. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/__init__.py +0 -0
  41. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/const.py +0 -0
  42. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/handlers.py +0 -0
  43. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/profile.py +0 -0
  44. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/test_handlers.py +0 -0
  45. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/test_init.py +0 -0
  46. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/test_profile.py +0 -0
  47. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/test_util.py +0 -0
  48. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/types.py +0 -0
  49. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/cow/util.py +0 -0
  50. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/__init__.py +0 -0
  51. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/dev_null.py +0 -0
  52. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/json_echo.py +0 -0
  53. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/meta.py +0 -0
  54. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/moocow.py +0 -0
  55. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/number.py +0 -0
  56. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/examples/scarecrow.py +0 -0
  57. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/builder.py +0 -0
  58. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/config.py +0 -0
  59. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/load.py +0 -0
  60. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/test_config.py +0 -0
  61. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/test_load.py +0 -0
  62. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/manager/test_manager.py +0 -0
  63. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/test_scheduler.py +0 -0
  64. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/herd/types.py +0 -0
  65. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/test_validators.py +0 -0
  66. {roboherd-0.1.8 → roboherd-0.1.10}/roboherd/validators.py +0 -0
@@ -12,5 +12,4 @@ steps:
12
12
  CODEBERG_TOKEN: { from_secret: codeberg_token }
13
13
  CODEBERG_OWNER: bovine
14
14
  CODEBERG_REPO: roboherd
15
- caddle_drive_connection_string:
16
- { from_secret: caddle_drive_connection_string }
15
+ CONNECTION_STRING: { from_secret: caddle_drive_connection_string }
@@ -9,7 +9,7 @@ depends_on: ["publish_pypi"]
9
9
 
10
10
  steps:
11
11
  publish_docker_latest:
12
- image: woodpeckerci/plugin-docker-buildx
12
+ image: woodpeckerci/plugin-docker-buildx:6.0.2
13
13
  settings:
14
14
  platforms: linux/amd64,linux/arm64
15
15
  repo: helgekr/roboherd
@@ -1,5 +1,15 @@
1
1
  # Changes to roboherd
2
2
 
3
+ ## 0.1.10
4
+
5
+ - Add check command [roboherd#48](https://codeberg.org/bovine/roboherd/issues/48)
6
+
7
+ ## 0.1.9
8
+
9
+ - Ensure roboherd terminates on connection close [roboherd#1](https://codeberg.org/bovine/roboherd/issues/1)
10
+ - Make doc links clickable [roboherd#44](https://codeberg.org/bovine/roboherd/issues/44)
11
+ - Document how to work with cattle_grid in `README.md`
12
+
3
13
  ## 0.1.8 ([Milestone](https://codeberg.org/bovine/roboherd/milestone/11129))
4
14
 
5
15
  - Add ConfigOverrides model [roboherd#43](https://codeberg.org/bovine/roboherd/issues/43)
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: roboherd
3
+ Version: 0.1.10
4
+ Summary: A Fediverse bot framework
5
+ Project-URL: Documentation, https://bovine.codeberg.page/roboherd/
6
+ Project-URL: Repository, https://codeberg.org/bovine/roboherd
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: aiohttp>=3.11.12
9
+ Requires-Dist: almabtrieb[mqtt]>=0.2
10
+ Requires-Dist: apscheduler>=3.11.0
11
+ Requires-Dist: click>=8.1.8
12
+ Requires-Dist: cron-descriptor>=1.4.5
13
+ Requires-Dist: dynaconf>=3.2.6
14
+ Requires-Dist: fast-depends>=2.4.12
15
+ Requires-Dist: tomli-w>=1.1.0
16
+ Requires-Dist: watchfiles>=1.0.4
17
+ Provides-Extra: bovine
18
+ Requires-Dist: bovine>=0.5.15; extra == 'bovine'
19
+ Requires-Dist: markdown>=3.7; extra == 'bovine'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Roboherd
23
+
24
+ Roboherd is a framework for building Fediverse bots
25
+ using the [Cattle Drive Protocol](https://bovine.codeberg.page/cattle_grid/cattle_drive/).
26
+
27
+ For more information, see the [documentation](https://bovine.codeberg.page/roboherd/) or the [repository](https://codeberg.org/bovine/roboherd/).
28
+
29
+ ## Developping with cattle_grid
30
+
31
+ In your catle_grid `config` directory add a roboherd user, e.g.
32
+ a file `testing.toml` with content
33
+
34
+ ```toml
35
+ [testing]
36
+ enable = true
37
+
38
+ [[testing.accounts]]
39
+ name = "herd"
40
+ password = "pass"
41
+ permissions = ["admin"]
42
+ ```
43
+
44
+ Configure roboherd via `roboherd.toml`, e.g.
45
+
46
+ ```toml
47
+ base_url = "http://abel"
48
+ connection_string = "ws://herd:pass@localhost:3000/ws/"
49
+
50
+ [cow.rooster]
51
+ bot = "roboherd.examples.rooster:bot"
52
+ ```
53
+
54
+ This will trigger a periodic message to cattle_grid.
55
+
56
+ ### nginx for cattle_grid
57
+
58
+ The nginx in the `cattle_grid` configuration should forward the path `/ws/` to
59
+ rabbitmq (supporting mqtt over websockets)
60
+
61
+ ```nginx
62
+ server {
63
+ listen 80;
64
+
65
+ location /ws/ {
66
+ proxy_pass http://rabbitmq:15675;
67
+ proxy_http_version 1.1;
68
+ proxy_set_header Upgrade $http_upgrade;
69
+ proxy_set_header Connection $connection_upgrade;
70
+ proxy_read_timeout 86400; # neccessary to avoid websocket timeout disconnect
71
+ proxy_send_timeout 86400; # neccessary to avoid websocket timeout disconnect
72
+ proxy_redirect off;
73
+ proxy_buffering off;
74
+ }
75
+ }
76
+ ```
77
+
78
+ similarly `nginx` should forward port 80 to 3000 (in the docker compose file).
@@ -0,0 +1,57 @@
1
+ # Roboherd
2
+
3
+ Roboherd is a framework for building Fediverse bots
4
+ using the [Cattle Drive Protocol](https://bovine.codeberg.page/cattle_grid/cattle_drive/).
5
+
6
+ For more information, see the [documentation](https://bovine.codeberg.page/roboherd/) or the [repository](https://codeberg.org/bovine/roboherd/).
7
+
8
+ ## Developping with cattle_grid
9
+
10
+ In your catle_grid `config` directory add a roboherd user, e.g.
11
+ a file `testing.toml` with content
12
+
13
+ ```toml
14
+ [testing]
15
+ enable = true
16
+
17
+ [[testing.accounts]]
18
+ name = "herd"
19
+ password = "pass"
20
+ permissions = ["admin"]
21
+ ```
22
+
23
+ Configure roboherd via `roboherd.toml`, e.g.
24
+
25
+ ```toml
26
+ base_url = "http://abel"
27
+ connection_string = "ws://herd:pass@localhost:3000/ws/"
28
+
29
+ [cow.rooster]
30
+ bot = "roboherd.examples.rooster:bot"
31
+ ```
32
+
33
+ This will trigger a periodic message to cattle_grid.
34
+
35
+ ### nginx for cattle_grid
36
+
37
+ The nginx in the `cattle_grid` configuration should forward the path `/ws/` to
38
+ rabbitmq (supporting mqtt over websockets)
39
+
40
+ ```nginx
41
+ server {
42
+ listen 80;
43
+
44
+ location /ws/ {
45
+ proxy_pass http://rabbitmq:15675;
46
+ proxy_http_version 1.1;
47
+ proxy_set_header Upgrade $http_upgrade;
48
+ proxy_set_header Connection $connection_upgrade;
49
+ proxy_read_timeout 86400; # neccessary to avoid websocket timeout disconnect
50
+ proxy_send_timeout 86400; # neccessary to avoid websocket timeout disconnect
51
+ proxy_redirect off;
52
+ proxy_buffering off;
53
+ }
54
+ }
55
+ ```
56
+
57
+ similarly `nginx` should forward port 80 to 3000 (in the docker compose file).
@@ -5,6 +5,15 @@ so by connecting to a server through the [Cattle Drive protocol](https://bovine.
5
5
  configured the connection, all other tasks can be done using
6
6
  python code and a toml configuration file.
7
7
 
8
+ The serer roboherd connects to is specified through a connection string,
9
+ see [Configuration](#configuration).
10
+
11
+ ## Usage examples
12
+
13
+ - Examples of basic usage can be found in [roboherd.examples](https://codeberg.org/bovine/roboherd/src/branch/main/roboherd/examples)
14
+ - [release_helper](https://codeberg.org/helge/release_helper) automates the release of projects on codeberg and subsequent announcement on the Fediverse
15
+
16
+
8
17
  ## Tour of the functionality
9
18
 
10
19
  We will now tour how to write bots with Roboherd. We start with
@@ -40,6 +40,8 @@ markdown_extensions:
40
40
  - admonition
41
41
  - pymdownx.details
42
42
  - mkdocs-click
43
+ - toc:
44
+ permalink: true
43
45
  plugins:
44
46
  - search
45
47
  - mkdocstrings:
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "roboherd"
3
- version = "0.1.8"
3
+ version = "0.1.10"
4
4
  description = "A Fediverse bot framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
8
  "aiohttp>=3.11.12",
9
- "almabtrieb[mqtt]>=0.1.0a1",
9
+ "almabtrieb[mqtt]>=0.2",
10
10
  "apscheduler>=3.11.0",
11
11
  "click>=8.1.8",
12
12
  "cron-descriptor>=1.4.5",
@@ -13,6 +13,7 @@ from roboherd.register import register as run_register
13
13
  from roboherd.validators import validators
14
14
 
15
15
  logging.basicConfig(level=logging.INFO)
16
+ logging.captureWarnings(True)
16
17
 
17
18
 
18
19
  @click.group()
@@ -28,7 +29,7 @@ logging.basicConfig(level=logging.INFO)
28
29
  )
29
30
  @click.option("--config_file", default="roboherd.toml", help="Configuration file")
30
31
  @click.pass_context
31
- def main(ctx, connection_string, base_url, config_file):
32
+ def main(ctx: click.Context, connection_string: str, base_url: str, config_file: str):
32
33
  """Configuration is usually loaded from the config_file. These options can be overwritten by passing as a command line argument."""
33
34
  settings = dynaconf.Dynaconf(
34
35
  settings_files=[config_file],
@@ -43,17 +44,37 @@ def main(ctx, connection_string, base_url, config_file):
43
44
  if connection_string:
44
45
  ctx.obj["connection_string"] = connection_string
45
46
  else:
46
- ctx.obj["connection_string"] = settings.connection_string
47
+ ctx.obj["connection_string"] = settings.connection_string # type: ignore
47
48
 
48
49
  if base_url:
49
50
  ctx.obj["base_url"] = base_url
50
51
  else:
51
- ctx.obj["base_url"] = settings.base_url
52
+ ctx.obj["base_url"] = settings.base_url # type: ignore
52
53
 
53
54
 
54
55
  @main.command()
56
+ @click.option("--fail", is_flag=True, default=False, help="Fail if actors do not exist")
55
57
  @click.pass_context
56
- def run(ctx):
58
+ def check(ctx: click.Context, fail: bool):
59
+ """Checks that the connection is configured correctly"""
60
+
61
+ create_connection(ctx)
62
+
63
+ herd = RoboHerd(base_url=ctx.obj["base_url"])
64
+
65
+ settings = ctx.obj["settings"]
66
+
67
+ if settings.get("cow"):
68
+ herd.manager = HerdManager.from_settings(settings)
69
+ asyncio.run(herd.check(ctx.obj["connection"], raise_if_cows_to_create=fail))
70
+ else:
71
+ click.echo("No cows specified")
72
+ exit(1)
73
+
74
+
75
+ @main.command()
76
+ @click.pass_context
77
+ def run(ctx: click.Context):
57
78
  """Runs the roboherd by connecting to the server."""
58
79
 
59
80
  create_connection(ctx)
@@ -90,7 +111,7 @@ def watch(ctx):
90
111
  prompt=True,
91
112
  )
92
113
  @click.option("--fediverse", help="Fediverse handle", prompt=True)
93
- def register(ctx, name, password, fediverse):
114
+ def register(ctx: click.Context, name: str, password: str, fediverse: str):
94
115
  """Registers a new account on dev.bovine.social. All three options are required. If not provided, you will be prompted for them."""
95
116
 
96
117
  if os.path.exists(ctx.obj["config_file"]):
@@ -5,6 +5,7 @@ from .meta import meta_information
5
5
  bot = RoboCow.create(
6
6
  handle="rooster",
7
7
  name="The crowing rooster 🐓",
8
+ description="I'm a rooster that crows at a set frequency.",
8
9
  meta_information=meta_information,
9
10
  )
10
11
 
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import logging
3
3
 
4
- from typing import List, Tuple
5
4
  from dataclasses import dataclass, field
6
5
 
7
6
  from roboherd.cow import RoboCow, CronEntry
@@ -20,7 +19,7 @@ class RoboHerd:
20
19
  base_url: str = "http://abel"
21
20
 
22
21
  manager: HerdManager = field(default_factory=HerdManager)
23
- cows: List[RoboCow] = field(default_factory=list)
22
+ cows: list[RoboCow] = field(default_factory=list) # type: ignore
24
23
 
25
24
  async def run(self, connection: Almabtrieb):
26
25
  async with connection:
@@ -28,12 +27,26 @@ class RoboHerd:
28
27
  await self.startup(connection)
29
28
  await self.process(connection)
30
29
 
30
+ async def check(
31
+ self, connection: Almabtrieb, raise_if_cows_to_create: bool = False
32
+ ):
33
+ async with connection:
34
+ if not connection.information:
35
+ raise Exception("Could not get information from server")
36
+ self.validate(connection)
37
+ cows_to_create = self.manager.cows_to_create(connection.information.actors)
38
+
39
+ if len(cows_to_create) > 0:
40
+ print("Missing cows: " + ", ".join([x.name for x in cows_to_create]))
41
+
42
+ if raise_if_cows_to_create:
43
+ raise Exception("Missing cows")
44
+
31
45
  async def startup(self, connection: Almabtrieb):
32
46
  if not connection.information:
33
47
  raise Exception("Could not get information from server")
34
48
 
35
49
  self.cows = self.manager.existing_cows(connection.information.actors)
36
-
37
50
  cows_to_create = self.manager.cows_to_create(connection.information.actors)
38
51
 
39
52
  for cow_config in cows_to_create:
@@ -63,8 +76,12 @@ class RoboHerd:
63
76
  scheduler = HerdScheduler(self.cron_entries(), connection)
64
77
  scheduler.create_task(tg)
65
78
 
66
- def validate(self, connection):
79
+ connection.add_on_disconnect(scheduler.stop)
80
+
81
+ def validate(self, connection: Almabtrieb):
67
82
  result = connection.information
83
+ if result is None:
84
+ raise ValueError("information not retrieved from connection")
68
85
 
69
86
  logger.info("Got base urls: %s", ",".join(result.base_urls))
70
87
 
@@ -76,7 +93,7 @@ class RoboHerd:
76
93
  )
77
94
  raise ValueError("Incorrectly configured base url")
78
95
 
79
- def cron_entries(self) -> List[Tuple[RoboCow, CronEntry]]:
96
+ def cron_entries(self) -> list[tuple[RoboCow, CronEntry]]:
80
97
  """Returns the cron entries of all cows"""
81
98
 
82
99
  result = []
@@ -86,7 +103,7 @@ class RoboHerd:
86
103
 
87
104
  return result
88
105
 
89
- def incoming_handlers(self) -> List[RoboCow]:
106
+ def incoming_handlers(self) -> list[RoboCow]:
90
107
  result = []
91
108
  for cow in self.cows:
92
109
  if cow.internals.handlers.has_handlers:
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
 
3
- from typing import List
4
3
  from dataclasses import dataclass, field
5
4
 
6
5
  from almabtrieb.model import ActorInformation
@@ -21,7 +20,7 @@ class HerdManager:
21
20
  def from_settings(settings):
22
21
  return HerdManager(herd_config=HerdConfig.from_settings(settings))
23
22
 
24
- def existing_cows(self, actors: List[ActorInformation]) -> List[RoboCow]:
23
+ def existing_cows(self, actors: list[ActorInformation]) -> list[RoboCow]:
25
24
  existing_cows = []
26
25
 
27
26
  for info in actors:
@@ -28,12 +28,15 @@ class HerdProcessor:
28
28
  for cow in self.incoming_handlers:
29
29
  actor_id_to_cow_map[cow.internals.actor_id] = cow
30
30
 
31
+ logger.info("Incoming processing started for %s cows", len(actor_id_to_cow_map))
32
+
31
33
  async for msg in connection.incoming():
32
34
  actor_id = msg["actor"]
33
35
 
34
36
  cow = actor_id_to_cow_map.get(actor_id)
35
- logger.info(cow)
36
37
  if cow:
37
38
  await cow.internals.handlers.handle(
38
39
  msg, "incoming", connection, actor_id, cow=cow
39
40
  )
41
+
42
+ logger.warning("Process incoming ended")
@@ -19,22 +19,30 @@ logger = logging.getLogger(__name__)
19
19
  class HerdScheduler:
20
20
  entries: List[Tuple[RoboCow, CronEntry]]
21
21
  connection: Almabtrieb
22
+ scheduler: AsyncIOScheduler | None = None
23
+ task: asyncio.Task | None = None
22
24
 
23
25
  def create_task(self, task_group: asyncio.TaskGroup):
24
26
  if len(self.entries) == 0:
25
27
  logger.info("No tasks to schedule")
26
28
  return
27
- task_group.create_task(self.run())
29
+ if self.task:
30
+ raise Exception("Task already running")
31
+
32
+ self.task = task_group.create_task(self.run())
28
33
 
29
34
  async def run(self):
30
35
  if len(self.entries) == 0:
31
36
  return
32
37
 
33
- scheduler = AsyncIOScheduler()
38
+ if self.scheduler:
39
+ raise Exception("Scheduler already exists")
40
+
41
+ self.scheduler = AsyncIOScheduler()
34
42
 
35
43
  for cow, entry in self.entries:
36
44
  trigger = CronTrigger.from_crontab(entry.crontab)
37
- scheduler.add_job(
45
+ self.scheduler.add_job(
38
46
  inject(entry.func),
39
47
  trigger=trigger,
40
48
  kwargs={
@@ -44,7 +52,14 @@ class HerdScheduler:
44
52
  },
45
53
  )
46
54
 
47
- scheduler.start()
55
+ self.scheduler.start()
48
56
 
49
57
  while True:
50
58
  await asyncio.sleep(60 * 60)
59
+
60
+ async def stop(self):
61
+ logger.warning("Stopping scheduler")
62
+ if self.scheduler:
63
+ self.scheduler.shutdown()
64
+ if self.task:
65
+ self.task.cancel()
@@ -6,11 +6,11 @@ from roboherd.examples.dev_null import bot as dev_null
6
6
 
7
7
  from roboherd.cow import CronEntry
8
8
 
9
- from . import RoboHerd
9
+ from . import RoboHerd, RoboCow
10
10
 
11
11
 
12
12
  @pytest.mark.parametrize("cow, length", [(moocow, 0), (bot, 1)])
13
- def test_cron_entries(cow, length):
13
+ def test_cron_entries(cow: RoboCow, length: int):
14
14
  manager = RoboHerd(cows=[cow])
15
15
 
16
16
  assert len(manager.cron_entries()) == length
@@ -2,7 +2,9 @@ import aiohttp
2
2
  import tomli_w
3
3
 
4
4
 
5
- def create_config(name, password, domain="dev.bovine.social"):
5
+ def create_config(
6
+ name: str, password: str, domain: str = "dev.bovine.social"
7
+ ) -> dict[str, bool | str]:
6
8
  """
7
9
 
8
10
  ```
@@ -21,7 +23,7 @@ def create_config(name, password, domain="dev.bovine.social"):
21
23
  }
22
24
 
23
25
 
24
- async def register(config_file, name, password, fediverse):
26
+ async def register(config_file: str, name: str, password: str, fediverse: str):
25
27
  async with aiohttp.ClientSession() as session:
26
28
  result = await session.post(
27
29
  "https://dev.bovine.social/register",
@@ -8,7 +8,7 @@ from almabtrieb import Almabtrieb
8
8
  logger = logging.getLogger(__name__)
9
9
 
10
10
 
11
- def parse_connection_string(connection_string: str) -> dict:
11
+ def parse_connection_string(connection_string: str) -> dict[str, str | int | None]:
12
12
  """
13
13
  Parse a connection string into a dictionary of connection parameters.
14
14
 
@@ -43,7 +43,7 @@ def parse_connection_string(connection_string: str) -> dict:
43
43
  }
44
44
 
45
45
 
46
- def create_connection(ctx):
46
+ def create_connection(ctx: click.Context):
47
47
  connection_string = ctx.obj["connection_string"]
48
48
  base_url = ctx.obj["base_url"]
49
49