writing an extension install script

42 views
Skip to first unread message

bell...@gmail.com

unread,
Aug 30, 2026, 9:53:34 PM (3 days ago) Aug 30
to weewx-development
How does one add a service either (1) after an existing service or (2) prior to a service?
This the situation where it needs to handle an event between the two services handling the event.
Thanks.rich

Tom Keffer

unread,
Aug 31, 2026, 8:42:10 AM (3 days ago) Aug 31
to bell...@gmail.com, weewx-development
By default, the installer appends the new service to the end of the existing services. The extension installer calls function configure() just before finishing up. That's your opportunity to fiddle with the configuration dictionary and change the ordering. See the sample installer in the documentation.

The sample does not cover your specific case. Say you wanted to insert a new service 'user.mysvc.MySvc' right after StdQC. It would look something like this (NOT TESTED):

    def configure(self, engine):
        """Modify the configuration dictionary"""

        # Get the list of process services
        process_services = engine.config_dict['Engine']['Services']['process_service']
        # Build a new one with the ordering I want
        new_process_services = []
        did_modify = False
        for svc in process_services:
            if svc != 'user.mysvc.MySvc':
                new_process_services.append(svc)
            elif svc == 'weewx.engine.StdQC':
                new_process_services.append('user.mysvc.MySvc')
                did_modify = True
        # Check for the corner case where StdQC was not in the list. In that case, append
        if not did_modify:
                new_process_services.append('user.mysvc.MySvc')
                did_modify = True
        engine.config_dict['Engine']['Services']['process_service'] = new_process_services
        return did_modify

Needless to say, there are a million ways this can go wrong! If at all possible, it would be much better to figure out a way your service can live anywhere within its list. 

-tk


--
You received this message because you are subscribed to the Google Groups "weewx-development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to weewx-developm...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/weewx-development/05551e34-35a9-47f1-846a-ea7299f1d3f9n%40googlegroups.com.

Manuel Hilgert

unread,
Aug 31, 2026, 6:41:25 PM (2 days ago) Aug 31
to weewx-development

TL;DR: let a service declare run_after and run_before, and have the engine sort each service group at startup. Today every installer that needs a position rewrites the list by hand in configure().

Open question: is startup the right time to resolve this, or should it stay in the installer, so that weewx.conf remains the only source of truth?

Why configure() is not enough

An installer resolves the order once, at install time. It knows nothing about an extension installed later, and it does not notice when the neighbour is uninstalled. Every installer writes the same loop again.

The version posted above has a bug. The elif branch cannot be reached, since a service cannot be both user.mysvc.MySvc and weewx.engine.StdQCdid_modify stays False, the corner case at the end appends the service, and the list comes out unchanged.

Proposal

Two class attributes on StdService, both empty by default:

class MySvc(StdService): """Fill in a wind chill before StdQC gets to check it.""" run_after = ('weewx.engine.StdCalibrate',) run_before = ('weewx.engine.StdQC',)

The names are the strings from weewx.conf, so nothing has to import the neighbour.

Semantics:

  • They are constraints, not positions. There is no "immediately after".
  • A neighbour that is not installed drops the constraint.
  • With no constraints, a group comes out in the order given by weewx.conf.
  • A cycle is a startup error, naming the services involved.
  • A constraint naming a service in a later service group cannot be satisfied, since all_service_groups already fixes the group order. That can be reported. Today such a service just runs at the wrong time.

Implementation

loadServices() imports and instantiates in one pass. The classes have to be known before sorting, so it becomes two passes per group:

for service_group in all_service_groups: svcs = [svc for svc in config_dict['Engine']['Services'].get(service_group, []) if svc] # Get the classes first: the ordering constraints live on them. classes = {svc: weeutil.weeutil.get_object(svc) for svc in svcs} for svc in order_services(svcs, classes): self.service_obj.append(classes[svc](self, config_dict))

order_services() is Kahn's algorithm with a stable pick, about 25 lines. Not graphlib, which needs 3.9, while make vermin targets 3.7. Among the services whose constraints are satisfied, the one listed first in weewx.conf always wins, so the sort moves only what has to move.

A prototype gives:


Case

Result

Service appended at the end, run_after StdQC, run_before StdWXCalculate

ends up between the two

Neighbour not installed

constraint dropped, list unchanged

Two extensions, each after the other

ValueError, both names in the message

No constraints

list returned unchanged

What the user sees

The list in weewx.conf becomes a starting order rather than a guarantee. To keep that visible, log one INFO line per move:

Service user.mysvc.MySvc moved ahead of weewx.engine.StdQC (run_before)

Nothing is logged when nothing moves. A weectl service list showing the effective order would answer the original question directly.

What this does not solve

It orders services, it does not decouple them. A service that depends on a particular neighbour still has that dependency. The dependency would sit in the code that has it, where it can be checked, instead of in the line order of a configuration file.

Until then

The loop from above, with the elif fixed:

def configure(self, engine): """Insert the service right after StdQC""" svc_list = weeutil.weeutil.option_as_list( engine.config_dict['Engine']['Services']['process_service']) if 'user.mysvc.MySvc' in svc_list: svc_list.remove('user.mysvc.MySvc') try: idx = svc_list.index('weewx.engine.StdQC') + 1 except ValueError: # StdQC is not in the list. Append instead. idx = len(svc_list) svc_list.insert(idx, 'user.mysvc.MySvc') engine.config_dict['Engine']['Services']['process_service'] = svc_list return True

The removal is required, not cosmetic: weectl extension install has already appended the service to the end of the group by the time configure() runs.

Greg Troxel

unread,
Aug 31, 2026, 6:47:50 PM (2 days ago) Aug 31
to Manuel Hilgert, weewx-development
Is your email LLM generated?
It doesn't seem super-LLMish, but some of the structure is setting off
my LLM detector, perhaps falsely

(I have a personal policy of not paying attention to LLM output, and
believe that all LLM output shoudl be labeled as such -- but I know many
do not hold those views.)

Greg

bell...@gmail.com

unread,
Sep 1, 2026, 7:17:31 AM (2 days ago) Sep 1
to weewx-development
Agreed it should be avoided. Luckily I had a “brain fart”. I was thinking of how I was going to leverage the separation of archive record generation from persisting it. Then I remembered that generation is the service that dispatches the NEW_ARCHIVE_RECORD event. So, all I need to do is -  bind to the event in one of the service groups before the archive_services.
Details/information matter. My bad.
Thanks for taking your time to suggest a solution.
rich

Reply all
Reply to author
Forward
0 new messages