Skip to content

Creating API Functions for psucontrol#3

Open
Ian-Hendricksen wants to merge 4 commits into
t0:mainfrom
Ian-Hendricksen:main
Open

Creating API Functions for psucontrol#3
Ian-Hendricksen wants to merge 4 commits into
t0:mainfrom
Ian-Hendricksen:main

Conversation

@Ian-Hendricksen

Copy link
Copy Markdown
Contributor

I moved some functions from cli.py to a new script api.py which executes the commands of each script, and reformatted those same functions in cli.py to use API calls.

This change allows for both command-line interface control of the PSU along with integration of the PSU into external repositories such that the PSU can be controlled through Python with the new PSUController class in api.py. PSU discovery remains in cli.py, encouraging a user to use the command-line interface to discover their PSU, and then create an object with PSUController for its corresponding hostname.

Command-line interface control and script-based control were both verified before committing changes.

…ich can be called either from cli.py for command-line interfacing, or in external python scripts for integration with FPGA control software
Comment thread psucontrol/api.py Outdated
return 0
except requests.exceptions.RequestException as e:
print(f"Failed to control PSU: {e}", file=sys.stderr)
return 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is probably better off as an exception (rather than a "1" return code and a printed message)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, return the exception? Or instead, interrupt the process (i.e., don't catch the error, just let it stop the process)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied the 0/1 return from the original, so I thought it would be preferred to keep that as the return.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a CLI tool, returning an integer (0 on success, 1 on failure) is conventional - but that's UNIX command-line conventions, not Python conventions, so it's only relevant at the very top level (e.g. main).

When structuring library code, we should use the following convention:

  • library code uses exceptions for error paths, does not print, and (sparingly) uses logging only where required
  • setters (here, "control_power") do not return a value
  • getters (here, "status") return some meaningful value in Python-appropriate type (e.g. True/False, float, int, dict, ...)

So for example:

class PSUController():
    # simplest case: just let any exceptions escape
    def set_power(self, state : bool):
        do_thing()
        # no return needed - this is a setter, so there's nothing to say if we succeeded

    # or, if the underlying exception doesn't make sense in this context, re-raise it
    def set_power_alt(self, state : bool):
        try:
            do_thing()
        except SomeException as e:
            raise SomeOtherException("this is a more logical exception or string") from e

Comment thread psucontrol/api.py Outdated
timeout=self.timeout
)
response.raise_for_status()
print(f"PSU output {'enabled' if state else 'disabled'}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, if this is library code it should not be printing to stdout. Do you need these prints (are they useful?)

If you do want to emit text, consider using the logging API instead.

I think this is is generic feedback that applies to a couple of places in the PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's useful to have a printout showing that the output has indeed been changed (even if it's just psychological need)...

Would adding file=sys.stderr to the print command be what you mean?

Comment thread psucontrol/api.py Outdated
print(f" Temperature: {data.get('temp', 0):.1f} °C")
print(f" Fan speed: {data.get('fan_rpm', 0)} RPM")

return 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is another point that I realized after the pull request that I'd like to change. I would like this to return the JSON output if requested so that I can actually recover the status and then use it to make decisions about e.g., whether to continue a test or not.

…nstead of printing errors. return useful data (e.g., status values). moved flash back to cli.py since it doesnt require a hostname.
…rocesses to main (e.g., API requests). PSU discovery works, but API calls based on requests library not working. this commit is therefore untested and requires follow-up.
@gsmecher

Copy link
Copy Markdown
Contributor

Hi @Ian-Hendricksen - is this still a WIP? I just noticed it's still open and we should work towards merging or closing it.

@Ian-Hendricksen

Ian-Hendricksen commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Hey Graeme --- I had meant to reach out to you about this recently!

The WIP was not the best commit message ever. I was expecting more changes to come following the issues we were seeing with talking to the PSU over multiple connections, but I've been using my version of psucontrol recently and have had no issues (probably because I was the only one using it on our crate).

So, my last commit is working and I think we should merge/close this PR.

I have the CHORD F-Engine installed at DRAO, so I'd like to use the "official" psucontrol instead of cloning and pip installing my version.

Do let me know if there's anything else I can clean up in the code before we finalize things!

@gsmecher

Copy link
Copy Markdown
Contributor

Understood - I forgot about this PR (it wasn't the WIP that threw me off - but I am now trying to catch up). Unfortunately you and @BenCoullNeveu both have conflicting PRs open.

@BenCoullNeveu

Copy link
Copy Markdown
Contributor

My PR is still very much a WIP. I can try to edit my branch to resolve potential conflicts

@BenCoullNeveu

Copy link
Copy Markdown
Contributor

I've edited my code to include Ian's api.py file and relevant cli.py changes. There will likely be more api changes to do given my modifications, but at the moment this should hopefully allow for a merge. between the PRs.

@gsmecher

Copy link
Copy Markdown
Contributor

Great - that will allow us to merge this PR and drop the other one (and end up with both). Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants