Skip to content

Add ability to update custom Function module in-place#1516

Merged
kizniche merged 9 commits into
masterfrom
copilot/add-update-custom-function-module
Mar 11, 2026
Merged

Add ability to update custom Function module in-place#1516
kizniche merged 9 commits into
masterfrom
copilot/add-update-custom-function-module

Conversation

Copilot AI commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Updating a custom Function module required deleting all dependent Function instances, removing the module, re-uploading, and recreating everything — making iterative development painful.

Changes

  • ControllerMod form (forms_settings.py): New form with FileField for the replacement file and a submit button. controller_id is no longer duplicated here — it is supplied by ControllerDel which shares the same <form> tag per row.

  • settings_function_update(form_del, form) (utils_settings.py): New handler that reads controller_id from form_del (the shared ControllerDel form) and the replacement file from form. Validates by loading both the existing on-disk module and the uploaded module, extracting function_name_unique from each file's FUNCTION_INFORMATION dict, and comparing them. The uploaded file's filename is irrelevant — only the dict value determines a match. On success, overwrites the existing module file, triggers a frontend reload, and restarts the backend daemon only if at least one CustomController using this module is currently activated.

  • Route (routes_settings.py): Instantiates form_controller_update, dispatches to settings_function_update(form_controller_delete, form_controller_update) on POST, passes both forms to the template.

  • Template (function.html): Each row in the Imported Function Modules table renders a single controller_id hidden field (from form_controller_delete), a file-picker, and an "Update Controller Module" button alongside the existing Delete button, inside a single enctype="multipart/form-data" form with a JS confirmation dialog.

  • CHANGELOG.md: Feature entry added under 8.17.0.

  • test_utils_settings.py (new): 8 unit tests using split make_del_form/make_mod_form helpers matching the two-argument signature — covering success with no activated functions (frontend reload only), success with an activated function (frontend reload + daemon restart), overwrite of existing file content, no file submitted, empty filename submitted, different upload filename with matching dict unique name (succeeds), different upload filename with mismatched dict unique name (fails), and invalid Python file. A mock_mycodo_user fixture patches pwd.getpwnam/grp.getgrnam in system_pi to return the current process UID/GID so os.chown exercises its real code path without requiring a mycodo system account in CI.

  • .gitignore: Added /mycodo/flask_session/ to exclude test-generated session files; added !mycodo/tests/ negation so the new test file is tracked.

Original prompt

This section details on the original issue you should resolve

<issue_title>Add ability to update custom Function module</issue_title>
<issue_description>Issue: Currently, if there has been a custom function module added via the Config -> Custom Functions page (/settings/function), and that function has been added on the Functions page (/function), the only way to update that module is to delete all functions that use it, then delete the custom function, upload a new custom function, then create a new function on the /function page. This is tedious when developing a custom function, as it erases all setting and requires a lot of manual work to add the function(s) back.

Solution: It would be better to have the ability to simply select a file that will overwrite the custom function, allowing you to change the custom function code without having to delete your functions. This would be a new file select and "Upload" button next to the "Delete" button that currently exists on the Custom Functions page. There should be a warning when pressing this. The code changes to accomplish this should be minimal and conform to the current conventions used in the code, including spacing, commenting, phrases (e.g. "Are you sure you want to do this..." for the confirmation, etc.). Only make the code and comment changes, do not make any additional documentation docs or update any existing docs, as this is a minor change. Do update the CHANGELOG with a line indicating the new feature addition.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits March 10, 2026 18:42
Co-authored-by: kizniche <838427+kizniche@users.noreply.github.com>
Co-authored-by: kizniche <838427+kizniche@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ability to update custom function module Add ability to update custom Function module in-place Mar 10, 2026
@kizniche

Copy link
Copy Markdown
Owner

@copilot In utils.settings.py, you set "controller_device_name = form.controller_id.data" and then compare "controller_info.FUNCTION_INFORMATION['function_name_unique'].lower() != controller_device_name.lower()", however, this appears to be initially setting the function name to the filename of the uploaded file? The unique function name is contained within the module file, in the FUNCTION_INFORMATION dict, key function_name_unique. To compare the existing unique name to the uploaded file unique name, you will need to get the dict form the new file. Check the import code for how the file is validated, as there are several sanity checks before an uploaded file can be accepted. For this replacement, the unique name must be the same.

Copilot AI and others added 2 commits March 10, 2026 19:12
…estart when activated; add tests

Co-authored-by: kizniche <838427+kizniche@users.noreply.github.com>
Co-authored-by: kizniche <838427+kizniche@users.noreply.github.com>

Copilot AI commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot In utils.settings.py, you set "controller_device_name = form.controller_id.data" and then compare "controller_info.FUNCTION_INFORMATION['function_name_unique'].lower() != co...

Fixed in commit 50aeba5. The function now loads both the existing module from disk and the uploaded module, extracting function_name_unique from each file's FUNCTION_INFORMATION dict, then compares the two. This means the uploaded file's filename is entirely irrelevant — only the dict value matters. Tests test_update_different_filename_same_unique_name_succeeds and test_update_different_filename_different_unique_name_fails explicitly verify this behaviour.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • mycodo_influxdb
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/test_mycodo_flask/test_utils_settings.py -v -W ignore::DeprecationWarning (dns block)
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/ -W ignore::DeprecationWarning -q (dns block)
  • none
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/test_mycodo_flask/test_utils_settings.py -v -W ignore::DeprecationWarning (dns block)
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/ -W ignore::DeprecationWarning -q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@kizniche kizniche marked this pull request as ready for review March 11, 2026 14:00
@kizniche kizniche requested a review from Copilot March 11, 2026 14:00

Copilot AI left a comment

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.

Pull request overview

Adds an in-place “update/overwrite” flow for custom Function modules on the Settings → Function page, reducing the need to delete/recreate dependent Function instances during iterative development.

Changes:

  • Add a new ControllerMod form and POST handling to update an existing custom Function module by uploading a replacement file.
  • Implement settings_function_update() to validate FUNCTION_INFORMATION['function_name_unique'] matches the existing module, overwrite the on-disk module, reload frontend, and optionally restart the daemon if any dependent Functions are activated.
  • Add unit tests for update success/failure scenarios; update .gitignore and changelog accordingly.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
mycodo/mycodo_flask/utils/utils_settings.py Adds settings_function_update() implementation for module replacement + reload/restart logic.
mycodo/mycodo_flask/forms/forms_settings.py Introduces ControllerMod (hidden controller_id + upload field + submit).
mycodo/mycodo_flask/routes_settings.py Wires new form + POST dispatch and passes form to template.
mycodo/mycodo_flask/templates/settings/function.html Adds per-row file picker + “Replace” button next to Delete.
mycodo/tests/software_tests/test_mycodo_flask/test_utils_settings.py Adds unit tests covering update behavior and edge cases.
.gitignore / CHANGELOG.md Ignores session artifacts, ensures tests are tracked, and documents the feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mycodo/mycodo_flask/utils/utils_settings.py
Comment thread mycodo/mycodo_flask/utils/utils_settings.py Outdated
Comment thread mycodo/mycodo_flask/templates/settings/function.html Outdated
Comment thread mycodo/mycodo_flask/utils/utils_settings.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
kizniche and others added 2 commits March 11, 2026 10:19
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…oller_id in update handler

Co-authored-by: kizniche <838427+kizniche@users.noreply.github.com>

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • mycodo_influxdb
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/test_mycodo_flask/test_utils_settings.py -v -W ignore::DeprecationWarning (dns block)
  • none
    • Triggering command: /usr/bin/python python -m pytest tests/software_tests/test_mycodo_flask/test_utils_settings.py -v -W ignore::DeprecationWarning (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@kizniche kizniche merged commit ea265e4 into master Mar 11, 2026
7 of 8 checks passed
@kizniche kizniche deleted the copilot/add-update-custom-function-module branch March 11, 2026 14:28
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.

Add ability to update custom Function module

3 participants