feat(backend): add optional version constraint#402
Conversation
WalkthroughAdds a next-major version constant and a helper to generate/locate InvenTree/constraint.txt; integrates that constraint file into plugin installation commands and task flows; updates .gitignore to ignore the new constraint file. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CLI as CLI/Invoker
participant Tasks as tasks.py
participant Config as InvenTree.config
participant Installer as plugin.installer
participant Pip as pip
CLI->>Tasks: run install / plugins / update
activate Tasks
note right of Tasks `#f9f6ee`: pre-task: update_constraint
Tasks->>Config: get_constraint_file(force_write=True)
activate Config
Config-->>Tasks: path/to/InvenTree/constraint.txt
deactivate Config
Tasks->>Installer: install_plugins_file / install_plugin (constraint path)
activate Installer
Installer->>Config: get_constraint_file() %% ensure constraint exists
Config-->>Installer: path/to/constraint.txt
Installer->>Pip: pip install -c path/to/constraint.txt <packages/args>
deactivate Installer
deactivate Tasks
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Note Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
src/backend/InvenTree/InvenTree/version.py (1)
22-22: Confirm correctness of INVENTREE_SW_NXT_MAJOR and consider deriving it programmaticallyHard-coding '2.0' can drift from reality and requires manual updates. Given INVENTREE_SW_VERSION is '0.18.0 dev', please verify that '<2.0' is indeed the intended upper bound for the inventree-server package. If the bound should always be "next major", consider deriving it from INVENTREE_SW_VERSION to avoid maintenance overhead.
Would you like me to provide a small helper to compute the next-major string from INVENTREE_SW_VERSION, so this constant is not required?
.gitignore (1)
90-91: Remove duplicate ignore entry for locale_stats.jsonThe file is listed twice; the second entry is redundant.
Apply this diff to drop the duplicate line:
-src/backend/InvenTree/InvenTree/locale_stats.json src/backend/InvenTree/InvenTree/constraint.txtsrc/backend/InvenTree/plugin/installer.py (2)
249-255: Pip installs now apply constraints — good; also apply to file-based installsPassing -c here is the right place to enforce server-compat bounds during ad-hoc plugin installs.
To make the behavior consistent, consider also applying the same constraints when installing from the plugins file in install_plugins_file(). Otherwise, bulk installs bypass constraints.
Example (outside this hunk) for install_plugins_file():
from InvenTree.config import get_constraint_file cmd = ['install', '--disable-pip-version-check', '-U', '-r', str(pf), '-c', str(get_constraint_file())]Note: Unrelated to this PR but observed nearby — the -i flag is appended without an index URL argument when url is provided (Line 267). That will cause pip to error. Worth a follow-up tidy-up.
300-302: About the “TODO do not pin version”Agree with the direction. Pinning exact versions in plugins.txt can cause unnecessary churn. A pragmatic middle-ground is to write a compatible specifier (e.g. ~=x.y or >=x.y,<x.(y+1)) instead of ==x.y.z when a version is detected.
Would you like a patch to update update_plugins_file() to record a compatible range instead of an exact pin?
tasks.py (1)
460-469: Avoid double resolution of the constraint file pathCall get_constraint_file once and reuse the path for both writing and logging.
@task @state_logger('TASK13') def update_constraint(c): """Update the constraints file for plugin installations.""" from src.backend.InvenTree.InvenTree.config import get_constraint_file info('Generating constraint file for plugin installations...') - get_constraint_file(force_write=True) - info('New constraint file written to: ', get_constraint_file()) + path = get_constraint_file(force_write=True) + info('New constraint file written to: ', path)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
.gitignore(1 hunks)src/backend/InvenTree/InvenTree/config.py(1 hunks)src/backend/InvenTree/InvenTree/version.py(1 hunks)src/backend/InvenTree/plugin/installer.py(3 hunks)tasks.py(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Tests - inventree-python
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: Tests - inventree-python
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: build
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: matmair.InvenTree (Job Python39)
- GitHub Check: Build - Web UI
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Build - Web UI
🔇 Additional comments (4)
src/backend/InvenTree/plugin/installer.py (1)
15-15: LGTM: import of get_constraint_fileImporting the constraint helper here keeps installer concerns local and avoids a cyc dependency at module import time.
tasks.py (3)
502-507: Good call: pre=[update_constraint] ensures constraints are ready before installThis guards user-invoked “install” runs. Nice.
816-817: Correctly invokes update_constraint before install() in update() flowNecessary because pre-tasks do not run when calling install() directly from within update(). Good catch.
475-486: Confirmed: uv pip install supports -c/--constraint for local files
- Logging clearly shows the local constraint file path.
- Both
pip3 installanduv pip installreceive-c/--constraint, keeping install-time dependency resolution consistent.- Caveats to note:
- Remote‐URL constraint files (e.g.
--constraint https://…) aren’t supported byuv pip install.- Constraints are not applied to build-time dependencies in
uv; use--build-constraintorbuild-constraint-dependenciesinpyproject.tomlif you need to lock build-time packages.Also applies to lines 490–496.
| def get_constraint_file(force_write: bool = False) -> Union[str, Path]: | ||
| """Returns the path to the constraints file. | ||
|
|
||
| Args: | ||
| force_write (bool): If True, the constraints file will be created if it does not exist. | ||
|
|
||
| Returns: | ||
| Path or str: The path to the constraints file. | ||
| """ | ||
| from .version import INVENTREE_SW_NXT_MAJOR, INVENTREE_SW_VERSION | ||
|
|
||
| const_file = get_base_dir().joinpath('InvenTree', 'constraint.txt') | ||
| if force_write or (not const_file.exists()): | ||
| if not force_write: | ||
| logger.warning( | ||
| 'Constraint file does not exist - creating default file at %s', | ||
| const_file, | ||
| ) | ||
| ensure_dir(const_file.parent) | ||
| vers = INVENTREE_SW_VERSION.replace( | ||
| ' ', '' | ||
| ) # ensure that the uv resolver parses the condition correctly | ||
| const_file.write_text( | ||
| f'# InvenTree constraints file\ninventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n' | ||
| ) | ||
| return const_file | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make constraints generation PEP 440–safe; avoid writing dev suffix into specifier; clarify docstring
Using INVENTREE_SW_VERSION.replace(' ', '') can produce non-standard versions like '0.18.0dev'. Prefer deriving the base release 'X.Y.Z' to ensure pip/uv consistently parse the constraint. Also, the docstring currently says the file is created “if force_write is True”, but the implementation creates it if missing too.
Suggested changes:
- Normalize vers to the base release (drop dev suffix).
- Optionally import inventreeVersionTuple to avoid adding a new dependency.
- Clarify the docstring to reflect actual behavior.
Apply this diff within this hunk:
def get_constraint_file(force_write: bool = False) -> Union[str, Path]:
- """Returns the path to the constraints file.
+ """Returns the path to the constraints file.
- Args:
- force_write (bool): If True, the constraints file will be created if it does not exist.
+ Args:
+ force_write (bool): If True, (re)write the constraints file. If False, the file
+ is created on first use if it does not exist.
Returns:
Path or str: The path to the constraints file.
"""
- from .version import INVENTREE_SW_NXT_MAJOR, INVENTREE_SW_VERSION
+ from .version import (
+ INVENTREE_SW_NXT_MAJOR,
+ INVENTREE_SW_VERSION,
+ inventreeVersionTuple,
+ )
const_file = get_base_dir().joinpath('InvenTree', 'constraint.txt')
if force_write or (not const_file.exists()):
if not force_write:
logger.warning(
'Constraint file does not exist - creating default file at %s',
const_file,
)
ensure_dir(const_file.parent)
- vers = INVENTREE_SW_VERSION.replace(
- ' ', ''
- ) # ensure that the uv resolver parses the condition correctly
+ # Use base release (X.Y.Z) to avoid dev/local suffixes in constraints
+ try:
+ maj, minr, pat = inventreeVersionTuple(INVENTREE_SW_VERSION)
+ vers = f'{maj}.{minr}.{pat}'
+ except Exception:
+ # Fallback: strip all non-digit/dot characters
+ import re as _re
+ m = _re.search(r'(\d+\.\d+\.\d+)', str(INVENTREE_SW_VERSION))
+ vers = m.group(1) if m else '0.0.0'
const_file.write_text(
- f'# InvenTree constraints file\ninventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n'
+ f'# InvenTree constraints file\n'
+ f'inventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n'
)
return const_fileTwo minor follow-ups:
- The return type is effectively Path; consider narrowing the annotation to Path and updating the docstring accordingly.
- If desired, we can also compute the next-major bound dynamically from INVENTREE_SW_VERSION to eliminate INVENTREE_SW_NXT_MAJOR entirely.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def get_constraint_file(force_write: bool = False) -> Union[str, Path]: | |
| """Returns the path to the constraints file. | |
| Args: | |
| force_write (bool): If True, the constraints file will be created if it does not exist. | |
| Returns: | |
| Path or str: The path to the constraints file. | |
| """ | |
| from .version import INVENTREE_SW_NXT_MAJOR, INVENTREE_SW_VERSION | |
| const_file = get_base_dir().joinpath('InvenTree', 'constraint.txt') | |
| if force_write or (not const_file.exists()): | |
| if not force_write: | |
| logger.warning( | |
| 'Constraint file does not exist - creating default file at %s', | |
| const_file, | |
| ) | |
| ensure_dir(const_file.parent) | |
| vers = INVENTREE_SW_VERSION.replace( | |
| ' ', '' | |
| ) # ensure that the uv resolver parses the condition correctly | |
| const_file.write_text( | |
| f'# InvenTree constraints file\ninventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n' | |
| ) | |
| return const_file | |
| def get_constraint_file(force_write: bool = False) -> Union[str, Path]: | |
| """Returns the path to the constraints file. | |
| Args: | |
| force_write (bool): If True, (re)write the constraints file. If False, the file | |
| is created on first use if it does not exist. | |
| Returns: | |
| Path or str: The path to the constraints file. | |
| """ | |
| from .version import ( | |
| INVENTREE_SW_NXT_MAJOR, | |
| INVENTREE_SW_VERSION, | |
| inventreeVersionTuple, | |
| ) | |
| const_file = get_base_dir().joinpath('InvenTree', 'constraint.txt') | |
| if force_write or (not const_file.exists()): | |
| if not force_write: | |
| logger.warning( | |
| 'Constraint file does not exist - creating default file at %s', | |
| const_file, | |
| ) | |
| ensure_dir(const_file.parent) | |
| # Use base release (X.Y.Z) to avoid dev/local suffixes in constraints | |
| try: | |
| maj, minr, pat = inventreeVersionTuple(INVENTREE_SW_VERSION) | |
| vers = f'{maj}.{minr}.{pat}' | |
| except Exception: | |
| # Fallback: strip all non-digit/dot characters | |
| import re as _re | |
| m = _re.search(r'(\d+\.\d+\.\d+)', str(INVENTREE_SW_VERSION)) | |
| vers = m.group(1) if m else '0.0.0' | |
| const_file.write_text( | |
| f'# InvenTree constraints file\n' | |
| f'inventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n' | |
| ) | |
| return const_file |
|
Note Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/backend/InvenTree/plugin/installer.py (1)
268-277: Bug: -i option missing URL argument; disambiguate “index URL” vs “direct artifact URL”Currently, -i is appended without its required value, and full_pkg is set to url. This breaks installs (pip expects -i ), and conflates “index URL” with “direct URL to artifact”.
Apply this targeted fix:
- elif url: - install_name.append('-i') - full_pkg = url + elif url: + # Use custom index or direct artifact URL + # Index URL: pass via '-i <url>' and install by package name + # Direct artifact (wheel/sdist): install the URL directly + if re.match(r'^https?://', url) and not re.search(r'\.(whl|tar\.gz|zip)$', url): + if not packagename: + raise ValidationError(_('Package name must be provided when using a custom index URL')) + install_name.extend(['-i', url]) + full_pkg = packagename + else: + # Direct URL to wheel/sdist + full_pkg = url
♻️ Duplicate comments (1)
src/backend/InvenTree/InvenTree/config.py (1)
352-378: Make constraints PEP 440–safe and correct the docstring return typeReplacing spaces yields e.g. "0.18.0dev", which is not a canonical PEP 440 dev release. Prefer using the base release X.Y.Z to avoid pre/dev/local suffixes in constraints. Also, the docstring says “Path or str” but the function returns a Path.
This mirrors feedback given previously; repeating here for convenience.
Apply this diff within this hunk:
-def get_constraint_file(force_write: bool = False) -> Path: - """Returns the path to the constraints file. - - Args: - force_write (bool): If True, the constraints file will be created if it does not exist. - - Returns: - Path or str: The path to the constraints file. - """ - from .version import INVENTREE_SW_NXT_MAJOR, INVENTREE_SW_VERSION +def get_constraint_file(force_write: bool = False) -> Path: + """Return the path to the constraints file. + + Args: + force_write (bool): If True, (re)write the constraints file. If False, create it on first use if it does not exist. + + Returns: + Path: The path to the constraints file. + """ + from .version import ( + INVENTREE_SW_NXT_MAJOR, + INVENTREE_SW_VERSION, + inventreeVersionTuple, + ) @@ - if force_write or (not const_file.exists()): + if force_write or (not const_file.exists()): if not force_write: logger.warning( 'Constraint file does not exist - creating default file at %s', const_file, ) ensure_dir(const_file.parent) - vers = INVENTREE_SW_VERSION.replace( - ' ', '' - ) # ensure that the uv resolver parses the condition correctly - const_file.write_text( - f'# InvenTree constraints file\ninventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n' - ) + # Use base release X.Y.Z for a stable constraint (avoid dev/local suffixes) + try: + maj, minr, pat = inventreeVersionTuple(INVENTREE_SW_VERSION) + vers = f'{maj}.{minr}.{pat}' + except Exception: + import re as _re + m = _re.search(r'(\d+\.\d+\.\d+)', str(INVENTREE_SW_VERSION)) + vers = m.group(1) if m else '0.0.0' + const_file.write_text( + '# InvenTree constraints file\n' + f'inventree-server~={vers},<{INVENTREE_SW_NXT_MAJOR}\n' + )Follow-up: You could compute the next-major bound dynamically here (from INVENTREE_SW_VERSION) and drop INVENTREE_SW_NXT_MAJOR entirely, if desired.
🧹 Nitpick comments (4)
.gitignore (1)
90-91: Remove duplicate ignore entry and group generated file under a clearer sectionThere are two entries for locale_stats.json. Keep a single ignore rule. Also consider adding a small comment above constraint.txt to indicate it is generated.
Apply this diff:
# Locale stats file src/backend/InvenTree/InvenTree/locale_stats.json src/backend/InvenTree/InvenTree/licenses.txt -src/backend/InvenTree/InvenTree/locale_stats.json +# Generated constraints file src/backend/InvenTree/InvenTree/constraint.txtsrc/backend/InvenTree/InvenTree/version.py (1)
22-22: Hard-coded next-major may drift; confirm correctness and consider deriving programmaticallyINVENTREE_SW_VERSION is '0.18.0 dev' while INVENTREE_SW_NXT_MAJOR = '2.0'. Please confirm that the next breaking major is indeed 2.0 (not 1.0).
Optionally, avoid maintaining this constant by deriving the next-major bound where used (e.g., in get_constraint_file), based on INVENTREE_SW_VERSION.
src/backend/InvenTree/plugin/installer.py (1)
309-312: Pinning behavior TODOThe TODO “do not pin version” is noted, but current logic still pins to the resolved version when the caller didn’t pass a version. If/when you change this, ensure update_plugins_file receives version=None (and possibly is called outside the version block) so the file reflects unpinned specs unless the user requested pins.
tasks.py (1)
815-816: Redundant-looking call is intentional; optionally add a clarifying commentupdate() calls update_constraint(c) before install(c, ...). Since install is being called directly (not via invoke’s CLI), its pre=[update_constraint] may not execute. Keeping this explicit call makes sense.
Optionally add a short comment to explain why it’s called here even though install has a pre-task, to avoid future “cleanup” that would re-introduce a bug.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
.gitignore(1 hunks)src/backend/InvenTree/InvenTree/config.py(1 hunks)src/backend/InvenTree/InvenTree/version.py(1 hunks)src/backend/InvenTree/plugin/installer.py(4 hunks)tasks.py(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - inventree-python
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: Tests - inventree-python
- GitHub Check: build
- GitHub Check: matmair.InvenTree (Job Python39)
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Build - Web UI
🔇 Additional comments (6)
src/backend/InvenTree/plugin/installer.py (3)
15-15: Importing get_constraint_file is appropriateUsing a centralized helper for the constraints path keeps installer concerns focused and avoids path duplication.
138-146: Including the constraints file in the pip command looks goodPassing -c with the generated constraints aligns plugin installs with the server’s supported version range.
257-263: Apply constraints consistently to single-package installAdding -c to the single package install command is correct and keeps behavior consistent with file-based installs.
tasks.py (3)
460-468: Pre-generate constraints task is a good additionGenerating constraints via update_constraint ensures downstream install tasks have the file available. The logging is clear.
474-495: Propagating constraints to plugin installs via both pip and uv is solidThe logging now includes the constraint path; quoting the paths in the shell command avoids issues with spaces.
501-506: install task’s pre=[update_constraint] ensures constraints exist for CLI usageGood to have this pre-task to cover direct invocations (invoke install).
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/backend/InvenTree/InvenTree/version.py (1)
22-22: Consider renaming toINVENTREE_SW_NEXT_MAJORfor consistency.The abbreviation "NXT" is inconsistent with other constants in this file (e.g.,
INVENTREE_SW_VERSION), which use full words. Using "NEXT" would improve readability and maintain naming consistency.Apply this diff to use the full word:
-INVENTREE_SW_NXT_MAJOR = '2.0' # the next breaking major version +INVENTREE_SW_NEXT_MAJOR = '2.0' # the next breaking major versionNote: If you apply this change, ensure that all references to this constant in other files (e.g.,
config.py,installer.py) are also updated.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/backend/InvenTree/InvenTree/config.py(1 hunks)src/backend/InvenTree/InvenTree/version.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/backend/InvenTree/InvenTree/config.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Style [Typecheck]
- GitHub Check: Style [Documentation]
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Migration Test
- GitHub Check: Docker Build Test
- GitHub Check: Build - Web UI
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: matmair.InvenTree (Job Python39)
- GitHub Check: Build - Web UI
🔇 Additional comments (1)
src/backend/InvenTree/InvenTree/version.py (1)
22-22: Original review comment contains inaccurate premises and should be disregarded.Verification shows
INVENTREE_SW_NXT_MAJORis used in only one place:config.py(lines 367, 381) for generating plugin version constraints. The constant is not imported or used ininstaller.pyortasks.pyas the original review claimed. The usage is appropriate and focused; no additional documentation is necessary.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/backend/InvenTree/InvenTree/version.py (1)
22-22: LGTM! Consider spelling out "NEXT" for clarity.The constant value and purpose are clear. The abbreviation "NXT" is understandable but less standard than fully spelling out "NEXT" (e.g.,
INVENTREE_SW_NEXT_MAJOR).If you'd like to improve clarity, apply this diff:
-INVENTREE_SW_NXT_MAJOR = '2.0' # the next breaking major version +INVENTREE_SW_NEXT_MAJOR = '2.0' # the next breaking major versionNote: If you make this change, ensure the constant name is updated in consuming files (
config.py,plugin/installer.py, etc.).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/backend/InvenTree/InvenTree/version.py(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Style [Typecheck]
- GitHub Check: Tests - inventree-python
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - DB [PostgreSQL]
- GitHub Check: Style [Typecheck]
- GitHub Check: Tests - DB [SQLite] + Coverage 3.9
- GitHub Check: Tests - inventree-python
- GitHub Check: Tests - DB [MySQL]
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Docker Build Test
- GitHub Check: Migration Test
- GitHub Check: matmair.InvenTree (Job Python39)
- GitHub Check: Tests - API Schema Documentation
- GitHub Check: Build - Web UI
- GitHub Check: Build - Web UI
Summary by CodeRabbit
New Features
Chores