mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-25 14:54:17 +02:00
docs: update style guide
This commit is contained in:
+6
-16
@@ -117,7 +117,7 @@ Licensing is now accomplished using the [REUSE](https://reuse.software/spec-3.3/
|
||||
!!! tip "PR Scope"
|
||||
If you're unsure where to stop the scope of your PR, ask yourself: _"If I broke this up, could any parts of it still be used by the project in the meantime?"_
|
||||
|
||||
### Workflow Checks
|
||||
### :material-check-circle: Workflow Checks
|
||||
|
||||
When pushing your code, several automated [workflows](https://github.com/TagStudioDev/TagStudio/tree/main/.github/workflows) will check it against predefined tests and style checks. It's _highly recommended_ that you run these checks locally beforehand to avoid having to fight back-and-forth with the workflow checks inside your pull requests. These checks currently include:
|
||||
|
||||
@@ -126,7 +126,7 @@ When pushing your code, several automated [workflows](https://github.com/TagStud
|
||||
- [Pytest](developing.md#pytest) tests
|
||||
- REUSE [license compliance](#licenses)
|
||||
|
||||
### Runtime Requirements
|
||||
## :material-timer-play: Runtime Requirements
|
||||
|
||||
Code must function on all of the supported operating systems and versions:
|
||||
|
||||
@@ -134,18 +134,8 @@ Code must function on all of the supported operating systems and versions:
|
||||
- macOS 14.0+
|
||||
- Common Linux distributions and versions
|
||||
|
||||
## :material-file-document: Documentation Guidelines
|
||||
Final submitted code must **_NOT:_**
|
||||
|
||||
Documentation contributions include anything inside of the `docs/` folder as well as the `README.md`. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/).
|
||||
|
||||
- Use "[dash-case / kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)" for file and folder names
|
||||
- Follow the folder structure pattern
|
||||
- Don't add images or other media with excessively large file sizes
|
||||
- Provide alt text for embedded media
|
||||
- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
|
||||
|
||||
## :material-translate: Translation Guidelines
|
||||
|
||||
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
|
||||
|
||||
_Translation guidelines coming soon._
|
||||
- Contain superfluous or unnecessary logging statements
|
||||
- Cause unreasonable slowdowns to the program outside of a progress-indicated task
|
||||
- Cause undesirable visual glitches or artifacts on screen
|
||||
|
||||
+1
-1
@@ -214,7 +214,7 @@ From there, Git will automatically run through the hooks during commit actions!
|
||||
|
||||
You can automatically enter this development shell, and keep your user shell, with a tool like [direnv](https://direnv.net/). Some reference `.envrc` files are provided in the repository at [`contrib`](https://github.com/TagStudioDev/TagStudio/tree/main/contrib).
|
||||
|
||||
Two currently available are for [Nix](#nixos) and [uv](#installing-with-uv), to use one:
|
||||
Two currently available are for [Nix](#nix-nixos) and [uv](#installing-with-uv), to use one:
|
||||
|
||||
```sh
|
||||
ln -s .envrc-$variant .envrc
|
||||
|
||||
+187
-97
@@ -6,116 +6,206 @@ icon: material/sign-text
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! abstract "Prerequisite Reading"
|
||||
This guide assumes you've read the [Developing](developing.md) and [Contributing](contributing.md) pages first.
|
||||
|
||||
# :material-sign-text: Style Guide
|
||||
|
||||
## Formatting
|
||||
## :material-script-text: General Principles
|
||||
|
||||
Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older code may not be adhering to all of these guidelines, in which case _"do as I say, not as I do"..._
|
||||
- Write **clear**, **concise**, and **modular** code.
|
||||
- If the purpose of a peice of code is not obvious, a **short comment** should help explain it.
|
||||
- Remember to follow the rest of the [contribution guidelines](contributing.md)!
|
||||
|
||||
- Do your best to write clear, concise, and modular code.
|
||||
- This should include making methods private by default (e.g. `__method()`)
|
||||
- Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted
|
||||
- Keep a maximum column width of no more than **100** characters.
|
||||
- Code comments should be used to help describe sections of code that can't speak for themselves.
|
||||
- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add.
|
||||
- If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;)
|
||||
- Imports should be ordered alphabetically.
|
||||
- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
||||
- Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern.
|
||||
- When writing text for window titles or form titles, use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" capitalization. Your IDE may have a command to format this for you automatically, although some may incorrectly capitalize short prepositions. In a pinch you can use a website such as [capitalizemytitle.com](https://capitalizemytitle.com/) to check.
|
||||
- If it wasn't mentioned above, then stick to [**PEP-8**](https://peps.python.org/pep-0008/)!
|
||||
---
|
||||
|
||||
### Modules & Implementations
|
||||
## :material-text-box-check: Formatting
|
||||
|
||||
- **Do not** modify legacy library code in the `src/core/library/json/` directory
|
||||
- Avoid direct calls to `os`
|
||||
- Use `Pathlib` library instead of `os.path`
|
||||
- Use `platform.system()` instead of `os.name` and `sys.platform`
|
||||
- Don't prepend local imports with `tagstudio`, stick to `src`
|
||||
- Use the `logger` system instead of `print` statements
|
||||
- Avoid nested f-strings
|
||||
- Use HTML-like tags inside Qt widgets over stylesheets where possible
|
||||
|
||||
Final submitted code must **_NOT:_**
|
||||
|
||||
- Contain superfluous or unnecessary logging statements
|
||||
- Cause unreasonable slowdowns to the program outside of a progress-indicated task
|
||||
- Cause undesirable visual glitches or artifacts on screen
|
||||
|
||||
### Formatter Configs
|
||||
Linting in Python files is mostly taken care of by [ruff](developing.md#ruff) and is based on the rules declared in `pyproject.toml` and `.editorconfig`.
|
||||
|
||||
TagStudio provides an [EditorConfig](https://editorconfig.org/#example-file) file ([`.editorconfig`](https://github.com/TagStudioDev/TagStudio/blob/main/.editorconfig)) along with a [Prettier](https://prettier.io/) config file ([`.prettierrc.toml`](https://github.com/TagStudioDev/TagStudio/blob/main/.prettierrc.toml)) for formatting files other than .py files (Markdown, JSON, YAML, HTML, CSS, etc.). If editing these types of files it's recommended that you use a formatter that supports EditorConfig or has its settings matched to the EditorConfig and Prettier configs. Lastly, please pay attention to the `prettier-ignore` flags in present in some files if you are not using Prettier, as formatting these sections will break formatting used elsewhere such as the [MkDocs site](https://docs.tagstud.io/).
|
||||
|
||||
## Qt
|
||||
### :material-code-braces-box: Syntax Guidelines
|
||||
|
||||
As of writing this section, the QT part of the code base is quite unstructured and the View and Controller parts are completely intermixed[^1]. This makes maintenance, fixes and general understanding of the code base quite challenging, because the interesting parts you are looking for are entangled in a bunch of repetitive UI setup code. To address this we are aiming to more strictly separate the view and controller aspects of the QT frontend.
|
||||
- Python files should always follow the [**PEP 8**]() style guide conventions, unless specifically allowed otherwise.
|
||||
- The most notable exception in our project is the line length limit of **100** characters, which is enforced via ruff.
|
||||
- Internal Qt methods also use `camelCase` instead of `snake_case`, so overrides of those are commonly seen in the codebase.
|
||||
- Classes and attributes considered to be "[private](https://docs.python.org/3/tutorial/classes.html#private-variables)" should be prepended with a **single underscore** (e.g. `_internal_method()`).
|
||||
- If _functionally necessary_, an attribute name may be prepended with a double underscore to trigger "[name mangling](https://docs.python.org/3/reference/expressions.html#private-name-mangling)" (e.g. `__mangled_method()`).
|
||||
- Classes and methods should contain [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings _(this style is enforced via ruff)_.
|
||||
- Lists and JSON keys should be ordered by their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order) unless otherwise specified or readily indicated.
|
||||
- Some files have some or all of their attributes sorted. Please respect any established patterns like these in files you modify.
|
||||
|
||||
The general structure of the QT code base should look like this:
|
||||
---
|
||||
|
||||
## :material-filter-cog: Modules & Systems
|
||||
|
||||
### :fontawesome-brands-python: Python Modules
|
||||
|
||||
- Use `Pathlib` library instead of `os.path`
|
||||
- Use `platform.system()` instead of `os.name` or `sys.platform`
|
||||
- Avoid nested f-strings
|
||||
|
||||
### :material-tag: TagStudio Systems
|
||||
|
||||
- Translation keys can be accessed via bracket notation (e.g. `Translations["translation_key"]`) or with the `Translations.format()` method when a value needs to be passed to a placeholder in the translation.
|
||||
- Use HTML-like tags inside strings over explicit stylesheets where possible. The `Style` class provides several handy methods for formatting text with these.
|
||||
- Use the `format` method in the stylesheets class to format text headers.
|
||||
|
||||
---
|
||||
|
||||
## :material-folder-file: Project Layout
|
||||
|
||||
### :material-engine: Core <small>Backend</small>
|
||||
|
||||
Code that is integral to the core functionality of TagStudio and is UI-independent belongs under the `core/` directory. It's possible that some code that serves the UI can go here, as long as its purpose is to serve _any_ UI and is independent from Qt (e.g. file preview rendering).
|
||||
|
||||
```yaml title="Core Backend Directory Example"
|
||||
core/
|
||||
│
|
||||
├── library/ # The TagStudio library system
|
||||
│ │
|
||||
│ ├── alchemy/ # Current SQLite backend w/ SQLAlchemy ORM
|
||||
│ │
|
||||
│ ├── json/ # Read-only legacy JSON library system, kept for migrations
|
||||
│ │
|
||||
│ ├── query_lang/ # The query parser
|
||||
│ │
|
||||
│ │ # Library files that do not involve the SQLAlchemy ORM
|
||||
│ │ # NOTE: Future Non-SQLAlchemy library files will be placed here
|
||||
│ ├── refresh.py
|
||||
│ └── ...
|
||||
│
|
||||
└── utils/ # Utility classes and functions for the core
|
||||
```
|
||||
qt
|
||||
├── controllers
|
||||
│ ├── widgets
|
||||
│ │ └── preview_panel_controller.py
|
||||
│ └── main_window_controller.py
|
||||
├── views
|
||||
│ ├── widgets
|
||||
│ │ └── preview_panel_view.py
|
||||
│ └── main_window_view.py
|
||||
├── ts_qt.py
|
||||
└── mixed.py
|
||||
```
|
||||
|
||||
In this structure there are the `views` and `controllers` sub-directories. They have the exact same structure and for every `<component>_view.py` there is a `<component>_controller.py` at the same location in the other subdirectory and vice versa.
|
||||
|
||||
Typically the classes should look like this:
|
||||
|
||||
```py
|
||||
# my_cool_widget_view.py
|
||||
class MyCoolWidgetView(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__button = QPushButton()
|
||||
self.__color_dropdown = QComboBox()
|
||||
# ...
|
||||
self.__connect_callbacks()
|
||||
|
||||
def __connect_callbacks(self):
|
||||
self.__button.clicked.connect(self._button_click_callback)
|
||||
self.__color_dropdown.currentIndexChanged.connect(
|
||||
lambda idx: self._color_dropdown_callback(self.__color_dropdown.itemData(idx))
|
||||
)
|
||||
|
||||
def _button_click_callback(self):
|
||||
raise NotImplementedError()
|
||||
```
|
||||
|
||||
```py
|
||||
# my_cool_widget_controller.py
|
||||
class MyCoolWidget(MyCoolWidgetView):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def _button_click_callback(self):
|
||||
print("Button was clicked!")
|
||||
|
||||
def _color_dropdown_callback(self, color: Color):
|
||||
print(f"The selected color is now: {color}")
|
||||
```
|
||||
|
||||
Observe the following key aspects of this example:
|
||||
|
||||
- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code
|
||||
- The UI elements are in private variables
|
||||
- This enforces that the controller shouldn't directly access UI elements
|
||||
- Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc.
|
||||
- Instead of `_get_color()` there could also be a `_color` method marked with `@property`
|
||||
- The callback methods are already defined as protected methods with NotImplementedErrors
|
||||
- Defines the interface the callbacks
|
||||
- Enforces that UI events be handled
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! tip
|
||||
A good (non-exhaustive) rule of thumb is: If it requires a non-UI import, then it doesn't belong in the `*_view.py` file.
|
||||
!!! danger "Read-Only Legacy Code"
|
||||
**Do not modify** legacy library code in the `src/core/library/json/` directory!
|
||||
|
||||
[^1]: For an explanation of the Model-View-Controller (MVC) Model, checkout this article: [MVC Framework Introduction](https://www.geeksforgeeks.org/mvc-framework-introduction/).
|
||||
---
|
||||
|
||||
### :material-button-cursor: App UI <small>Frontend</small>
|
||||
|
||||
The application UI code is stored in the `qt/` directory, and contains all code specific to the Qt frontend. Qt widgets are built using an [MVC](https://www.geeksforgeeks.org/software-engineering/mvc-framework-introduction/) pattern, which is described in-depth below:
|
||||
|
||||
#### MVC Pattern
|
||||
|
||||
- **Models** are usually just objects from the [library](#core-backend).
|
||||
- The **controller** interacts with these, the _view_ does **not**.
|
||||
- **Views** are Qt layout classes that **_only_** contain the **layout** and **styling** for one or more widgets.
|
||||
- Class names are appended with `View`, and filenames appended with `_view`.
|
||||
- Not to be used standalone, but as the layouts for one or more controllers.
|
||||
- Some logic is acceptable in these classes if it serves to modularize the layout and allows controllers to influence how the layout is initialized.
|
||||
- **Reusable Layouts**
|
||||
- If a layout class is **_not meant_** to act as a view but instead be a generic layout, it belongs in the `views/layouts/` directory and the file should be appended with `_layout`.
|
||||
- **Styling Classes**
|
||||
- If a class is purely a source of reusable styling, it belongs in the `views/styles/` directory.
|
||||
|
||||
- **Controllers** are complete widgets or base classes for complete widgets.
|
||||
- Controller files simply take on the name of the final widget they create.
|
||||
- This also creates naming parity with other widgets that simply extend existing Qt widgets with additional logic.
|
||||
|
||||
```yaml title="Qt Frontend Directory Example"
|
||||
qt/
|
||||
│
|
||||
├── controllers/ # Widgets implementing views or extending other widgets
|
||||
│ ├── tag_suggest_box.py # Extends from `suggest_box.py`
|
||||
│ ├── suggest_box.py # Implements `suggest_box_view.py`
|
||||
│ ├── main_window.py
|
||||
│ └── ...
|
||||
│
|
||||
├── mixed/ # Files yet to be refactored into controllers and views
|
||||
│
|
||||
├── views/ # Everything related to widget layouts and appearances
|
||||
│ │
|
||||
│ ├── layouts/ # Layouts meant to be reused on their own inside other layouts
|
||||
│ │ ├── flow_layout.py
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── styles/ # Classes specific for styling
|
||||
│ │ ├── palette.py
|
||||
│ │ ├── stylesheets.py
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ │ # Views (layouts) that get implemented by controllers (widgets)
|
||||
│ ├── main_window_view.py
|
||||
│ ├── suggest_box_view.py
|
||||
│ └── ...
|
||||
│
|
||||
│ # Frontend classes that aren't related to widgets, like managers
|
||||
├── resource_manager.py
|
||||
├── cache_manager.py
|
||||
├── ts_qt.py # Qt Driver
|
||||
└── ...
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! warning "Pre-MVC UI Code"
|
||||
**Do not add** new files to the `qt/mixed/` directory! These files have yet to to be refactored per the current MVC style guidelines and the directory will be **removed** once those migrations have concluded.
|
||||
|
||||
Observe the following key aspects of the example below:
|
||||
|
||||
- The **view** extends from a Qt layout class, and the **controller** simply extends from QWidget.
|
||||
- The **controller** is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code.
|
||||
- The **view's** widgets that are intended to be controlled by the controller are **public**, while the **controller's** methods are largely **private**.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! example "MVC-Separated Widget Example"
|
||||
|
||||
```py title="views/my_cool_widget_view.py"
|
||||
class MyCoolWidgetView(QVBoxLayout):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.button = QPushButton()
|
||||
self.color_dropdown = QComboBox()
|
||||
|
||||
self.addWidget(self.button)
|
||||
self.addWidget(self.color_dropdown)
|
||||
```
|
||||
|
||||
```py title="controllers/my_cool_widget.py"
|
||||
class MyCoolWidget(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setLayout(MyCoolWidgetView())
|
||||
self._connect_callbacks()
|
||||
|
||||
def _connect_callbacks(self):
|
||||
self.layout().button.clicked.connect(self._button_click_callback)
|
||||
self.layout().color_dropdown.currentIndexChanged.connect(
|
||||
lambda idx: self._color_dropdown_callback(self.color_dropdown.itemData(idx)))
|
||||
|
||||
def _button_click_callback(self):
|
||||
print("Button was clicked!")
|
||||
|
||||
def _color_dropdown_callback(self, color: Color):
|
||||
print(f"The selected color is now: {color}")
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! tip "Tip for Logic Placement"
|
||||
A good rule of thumb is: If there's **conditional logic** after a widget has been created, it should probably go in a **controller**.
|
||||
|
||||
---
|
||||
|
||||
## :material-file-document: Documentation
|
||||
|
||||
Documentation contributions include anything inside the `docs/` folder as well as the `README.md`. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/). Some files such as the `CHANGELOG.md`, `CONTRIBUTING.md`, and `STYLE.md` are symlinked in the repo root from the `docs/` folder.
|
||||
|
||||
- Use "[dash-case / kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)" for file and folder names
|
||||
- Follow the folder structure pattern
|
||||
- Don't add images or other media with excessively large file sizes
|
||||
- Provide alt text for embedded media
|
||||
- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
|
||||
|
||||
---
|
||||
|
||||
## :material-translate: Translations
|
||||
|
||||
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
|
||||
|
||||
- Do not change text inside placeholders
|
||||
- Do not change the style tags inside translations
|
||||
- Use the glossary for term definitions
|
||||
|
||||
Reference in New Issue
Block a user