Instruction file imported from manweile/MusicProcessing (
.github/instructions/python.instructions.md). Copyright stays with the author.
Python Documentation Standards
- All new normal source Python files require a file-level Doxygen block.
- The following Python files do not require a file-level Doxygen block:
- stub/demo scripts
- Migration utilities
- one-off developer tools
- generated code
Python
Always use python doc strings
& doxygen javadoc style @ special commands.
- These Python rules apply only to
.pyfiles. - Use paired triple single quotes
''' ... '''for Python docstrings that are formatted as Doxygen documentation blocks.- These docstrings are both valid Python docstrings and the required Doxygen documentation format for this repository.
- Use
#for inline comments.
Shared Python Rules
- After the file block, order declarations as follows:
- Imports
- Module Level Constants
- Module Level Variables
- Classes
- Class function definitions
- Functions
- List items alphabetically within each group or subgroup unless a section below states otherwise.
- The final item of a sequence datatype should not have a trailing comma.
- Separate Import, Module Level Constants and Module Level Variables with a blank line.
- Separate classes from preceding code with 2 blank lines.
- Separate functions inside classes from preceding code with 2 blank lines.
- Separate functions outside classes from preceding code with 2 blank lines.
- In class and function definitions, the Doxygen documentation block is placed immediately after the definition line as the Python docstring.
- All python files end with a blank line.
Shared Doxygen Rules
- Use a one-line
@brief, terminated by a period. - Leave an empty line after
@brief. - Add a
@detailsblock. - Follow the Details Block Formatting Rules.
- Class and Test files documentation blocks may include
@noteblocks.@noteblocks are placed after the blank line following the@detailsblock and before any code example blocks.- Note blocks are followed by a blank line.
- Class and Test files documentation blocks may include code examples using the
@code{.text}and@endcodetags to illustrate command-line usage.- Code example blocks follow whatever is last of:
- The
@detailsblock - The
@noteblock
- The
- the
@endcodetag is followed by a blank line.
- Code example blocks follow whatever is last of:
- Use:
- if the function has a
selfparameter, include it in the@paramlist with the type of the class it belongs to.@param self {ClassName} The instance of the class the method belongs to.
@param name {type} descriptionfor input parameters, terminated by a period.@return name {type} descriptionwhen the function returns a specifically named value, terminated by a period.@return {type} descriptionwhen the function returns an unnamed value or expression, terminated by a period.@return {None} This function does not return a value.for functions that do not return anything.
- if the function has a
- Leave an empty line after the final
@paramor@returnblock. - Omit
@paramentirely for functions with no parameters. - Use
@exception {type} descriptionfor exceptions raised by the function, terminated by a period. - Omit
@exceptionentirely for functions that do not raise any exceptions. - All Doxygen documentation blocks in class definitions are followed by 1 blank line.
- All Doxygen documentation blocks in function definitions inside classes are followed by 1 blank line.
- All Doxygen documentation blocks in functions definitions outside of classes are followed by 1 blank line.
Column Length Rules
- All text in Python source files, including code, comments, and Doxygen documentation, must not exceed column 150.
- Inline comments are denoted by
#and do not have a period at the end - For imports:
- follow the Import Rules for placement of purpose comments.
- For long parameter lists:
- break parameters across continuation lines using standard Python indentation.
- align continuation lines with the opening delimiter or use a hanging indent of 4 spaces.
Details Block Formatting Rules
- Apply these rules to every
@detailsblock in Python Doxygen comments, including:- File-level documentation blocks
- Class documentation blocks
- Functions documentation blocks
- Standalone functions documentation blocks
- Write each
@detailsline as a concise & clear complete sentence ending with a period. - Keep each
@detailsline at or below column 150; shorten wording if necessary. - If the shortest clear wording still exceeds column 150, break the line after, in this order of preference:
- a comma
- a semicolon
- a coordinating conjunction: "for", "and", "nor", "but", "or", "yet", or "so"
- a preposition: "in", "on", "at", "to", "from", "by", "with", "about", "as", "of", or "for"
- If a line is broken, do not append a
<br>to the end of the line; instead, start the next line with a lowercase letter. - Do not append a
<br>to a single line@detailsblock. - Append a
<br>for lines ending with a period, except for the final line of a multiline@detailsblock. - Leave an empty line after the last line of a
@detailsblock.
File Block Rules
- All files start with a Doxygen file header block.
- The first line of the file header must follow the per file type header block rule.
- Main Files Header Block rules for the main file.
- Package Files Header Block rules for package files.
- Module Files Header Block rules for module files.
- Class Files Header Block rules for class files.
- Test Files Header Block rules for test files.
- Add
@authorGerald Manweiler, followed by an empty line. - Add
@briefProject Name ...in main filePackage ...in package filesDefines the ... modulein module filesDefines the ... classin class filesDefines the test ... classin test files- terminate
@brief ...with a period.
- Add an empty line after
@brief. - Add a
@detailsblock.- Follow the Details Block Formatting Rules.
- Add
@versionusing Semantic Versioning starting at 1.0.0, per semver.org inMAJOR.MINOR.PATCHformat. - Add
@date, followed by an empty line. - Add
@copyright @showdate "%Y" GWN Software. All rights reserved. - Leave one blank line after the file header block and before the first import.
Main File Header Block
- Main files are Python files that serve as the entry point of the application.
- Their next line is
@file ...where...is the file name - but not the relative file path; eg:@file main_file.py
Package Files Header Block
- Package files are
__init__.pyPython files whose primary purpose is to define a package. - Their header blocks start with
@package ...where...is the name python path; e.g.,src.audio_infofor thesrc/audio_infopackage. - Their next line is
@file ...where...is the name of the relative file path; eg:@file src/__init__.py
Module Files Header Block
- Module files are non-
__init__.pyPython files whose primary purpose is to define module-level functionality such as helper functions, constants, lightweight data containers, or support logic. - Their header blocks start with
@module ...where...is the module name. - Their next line is
@file ...where...is the file name - but not the relative file path; eg:@file module_file.py
Class Files Header Block
- Class files are Python files whose primary purpose is to define one main behavioral class.
- Their header blocks start with
@class ...where...is the primary class name. - Their next line is
@file ...where...is the file name - but not the relative file path; eg:@file class_file.py
Test Files Header Block
- Test files are
test_*.pyPython files whose primary purpose is to define unit tests for the project. - Their header blocks start with
@class ...where...is the name of the primary test class. - Their next line is
@file ...where...is the file name - but not the relative file path; eg:@file test_class_file.py
Import Rules
- Never use wildcard imports (e.g.,
from module import *). - Never use relative imports (e.g.,
from .module import ...). - Always use 1 import per line, never combine multiple imports in a single statement.
- Always use absolute imports for local modules in package files.
- all imports have an
# ...comment where...explains the purpose of the import statement. - The entire import statement plus same line comment must never exceed column 150.
- use the shortest possible comment that clearly conveys the purpose of the import.
- if the import statement does not exceed column 61, and the comment is less than 90 characters
- place the comment after the import statement on the same line, starting at column 61.
- if the import statement does not exceed column 61, but the comment is 90 characters or more
- place the comment immediately before the import statement.
- if the import statement exceeds column 61
- place the comment immediately before the import statement.
- Group imports in this order:
- Python standard modules - the modules installed with Python itself.
- third party modules - the modules installed via package managers like pip.
- local modules by usage category type- the modules defined within the project itself.
- List imports alphabetically within each subgroup and use consistent comment annotations.
- For all subgroups, imports via
importcome first, followed byfrom ... import ...statements- Maintain consistent comment annotations and alphabetical order in
importandfrom ... import ...statements.
- Maintain consistent comment annotations and alphabetical order in
- Leave a blank line after each subgroup.
- Precede each subgroup with:
# Standard Modules# Third Party Modules# Local Modules <type>, where<type>is one of the following:Methods- functions and procedures defined within the module, Eg.Local Module Methodsfor methods.Constants- constant values defined within the module, Eg.Local Module Constantsfor constants.Errors- custom error classes defined within the module, Eg.Local Module Errorsfor errors.Classes- classes defined within the module, Eg.Local Module Classesfor classes.
Module Level Variables and Constants
-
Have a Doxygen documentation block for all module level variables and constants.
-
In files that contain a class and module level variables or constants, the last module level variable or constant is followed by 2 blank lines.
-
The module level variables and constants always follow the logging set up.
-
The Doxygen documentation block should include:
## @var <variable_name>line for specifying the name of the module level variable or constant.# @brief <description>line for providing a brief summary of the variable or constant.# @details <details>line for providing additional information about the variable or constant.- Only module level variables and constants can have multiple
@detailslines if needed.- File-level, classes, class functions, and standalone functions documentation blocks can only have a single
@detailsline.
- File-level, classes, class functions, and standalone functions documentation blocks can only have a single
- Each
@detailsline should be: - be atomically granular
- be concise
- be relevant to the variable or constant it describes
- have a maximum of 150 characters per line
-
The final subgroup should be followed by 2 blank lines before the class definition or the next section.
-
Module level variables and constants have subgroups with this ordering:
- Variables that call functions
- Constant literals
- Constant Sets
- Constant Lists
-
within each subgroup, items should be listed in alphabetical order:
-
Example:
## @var directory # @brief Directory processing instance. # @details Provides directory processing functionality. directory = DirectoryProcessing() ## @var normalization # @brief Audio normalization instance. # @details Provides audio normalization functionality. normalization = AudioNormalization() ## @var subprocess_utils # @brief Subprocess utilities instance. # @details Provides subprocess utility functionality. subprocess_utils = SubprocessUtilities() ## @var TPOS # @brief ID3 disc-of-set tag. # @details Sets TPOS metadata. TPOS = "TPOS" ## @var TYER # @brief ID3 release-year tag. # @details Sets TYER metadata. TYER = "TYER" ## @var MP3_TIME_KEYS # @brief ID3 time keys. # @details Sets TYER metadata. # @details 'TYER' is the preferred key for ID3 time metadata. MP3_TIME_KEYS = { 'TYER', 'TORY', 'TDRC', 'TDOR', 'TXXX=originalyear' } ## @var M4A_TIME_KEYS # @brief MP4 (m4a) time keys. # @details Sets TYER metadata.<br> # @details'\xa9day' is the preferred key for MP4 time metadata. M4A_TIME_KEYS = { '\xa9day', '----:com.apple.iTunes:originalyear' }
Main File
- The main file is the Python file that serves as the entry point of the application.
- Follow Module Level Variables and Constants rules for placement of purpose comments.
- There is only one main file in the project.
- The main file has the shebang line
#!/usr/bin/env python3at the very first line of the file to specify the Python interpreter. - It contains the
if __name__ == "__main__":block to execute the main functionality. - The
if __name__ == "__main__":block follows Shared Doxygen rules for function documentation. - It may contain import statements for standard, third-party modules, and local modules.
Main File Logging Setup
-
The
main.pyfile must include a logging setup section.- The main file must import the standard modules
loggingandosmodules. - The main file must import the following local modules:
ERROR_LOG_FORMAT,LOG_EXT,GENERATED_PATH,LOG_DIR, andUTF8from the appropriate local module.
- The logging setup is placed after the import statements.
- The logging setup defines the log file name from the current file name.
- The logging setup appends
LOG_EXTto the file stem. - The logging setup creates the log file path with
GENERATED_PATH,LOG_DIR, and the log file name. - The logging setup calls
logging.basicConfig(...)to configure file logging. - The logging setup sets the logging level to
logging.DEBUG. - The logging setup uses
ERROR_LOG_FORMATfor the logging format. - The logging setup uses append mode with
filemode="a". - The logging setup uses
UTF8for the file encoding. - The logging setup defines a module logger with
logging.getLogger(__name__).
# Configure logging ## @var basename # @brief Module file base name. # @details Gets the current module file name for constructing the log file name. basename = os.path.basename(__file__) ## @var stem # @brief Module file stem. # @details Removes the extension from the module file base name. stem = os.path.splitext(basename)[0] ## @var file # @brief Module log file name. # @details Appends the configured log extension to the module file stem. file = stem + LOG_EXT ## @var log_filename # @brief Module log file path. # @details Joins the generated-files path, log directory, and module log file name. log_filename = os.path.join(GENERATED_PATH, LOG_DIR, file) # override the default logging level WARN to lowest level so we can log all levels logging.basicConfig(filename=log_filename, level=logging.DEBUG, format=ERROR_LOG_FORMAT, filemode="a", encoding=UTF8) ## @var logger # @brief Module logger. # @details Records application events using the module name. logger = logging.getLogger(__name__) - The main file must import the standard modules
Package Files
-
Package files are Python files whose primary purpose is to define a package.
-
Package files are Python files that expose public names for import by other modules.
-
Follow Module Level Variables and Constants rules for placement of purpose comments.
-
They may contain import statements for standard, third-party, and local modules.
-
Package files have an
__all__export list to specify which members are accessible when the package is imported. -
The
__all__export list is always last item in the file. -
__all__export lists have a@details <details>line for each item, in alphabetical order.- Each
@detailsline for an__all__item should explain how to import the item - have a maximum of 150 characters per line
- Example:
## @var __all__ # @brief Exposes class for importing by other modules. # @details In modules needing the class, add `from src.audio_info.audio_art import AudioArt` # @details In modules needing the class, add `from src.audio_info.audio_metadata import AudioMetadata` # @details In modules needing the class, add `from src.audio_info.audio_playlist import AudioPlaylist` # @details In modules needing the class, add `from src.audio_info.audio_utilities import AudioUtilities` __all__ = [ "AudioArt", "AudioMetadata", "AudioPlaylist", "AudioUtilities" ] - Each
Module Files
- Module files are Python files that define the functionality of a specific module within the package.
- Module files are Python files whose primary purpose is to define module-level functionality such as:
- helper functions, constants, lightweight data containers, or support logic.
- They may contain import statements for standard, third-party, and local modules.
- They may contain module-level variables and constants.
- Follow Module Level Variables and Constants rules for placement of purpose comments.
- They may contain classes and function definitions relevant to the module's purpose.
- They may contain stand alone functions relevant to the module's purpose.
Class Files
- Class files are Python files whose primary purpose is to define one main behavioral class.
- Class files reside in the
srcdirectory and its sub-directories, and follow the naming convention<class_name>.py. - They may contain import statements for standard, third-party, and local modules.
- They may contain module level variables and constants.
- Follow Module Level Variables and Constants rules for placement of purpose comments.
- They follow the Class File Garbage Collection rules as described above.
- They follow the Class File Logging Setup rules as described below.
- They contain the class definition and its methods.
Class File Garbage Collection
- All class files, excepting test files, must enable garbage collection.
- The class file must import the standard module
gc. - The garbage collection setup is placed after the import statements and before any other module-level code.
- The garbage collection setup calls
gc.enable(). - The garbage collection enable call is followed by a blank line.
- Example:
import gc # for garbage collection management
# ... other import statements for standard, third-party, and local modules
gc.enable()
Class File Logging Setup
- All class files, excepting test files, must include a logging setup section.
- The class file must import the standard modules
loggingandosmodules. - The class file must import the local module method
add_module_handler. - The logging setup is placed after the garbage collection.
- The logging setup defines a module logger named with
__name__. - The logging setup defines the module file basename for the logger file handler.
- The logging setup calls
add_module_handler(logger, basename). - The logging setup section is followed by a blank line.
- Example:
## @var logger
# @brief Logger instance for the module.
# @details Set the logger name to the module name.
logger = logging.getLogger(__name__)
## @var basename
# @brief Base name for the logger file handler log file.
# @details Get the module file name from the current file path.
basename = os.path.basename(__file__)
add_module_handler(logger, basename)
Test Files
- Test files are Python files whose primary purpose is to define unit tests for the project.
- Test files are Python files that contain test cases for the classes defined in the project.
- Test files can be ran directly and therefore:
- must include the shebang line
#!/usr/bin/env python3as the first line of the file. - must contain the
if __name__ == "__main__":block to execute the test functionality. - the
if __name__ == "__main__":block is placed at the end of the test file. - The
if __name__ == "__main__":block follows Shared Doxygen rules for function documentation.
- must include the shebang line
- Files named
test_*.pyare implementation-only test sources, not public API. - Test files reside in the
testsdirectory and follow the naming conventiontest_*.py. - Test files are class files and should follow the same header and import rules as regular class files.
- Follow Module Level Variables and Constants rules for placement of purpose comments.
- Test files do not follow the Class File Logging Setup rules.
- Test cases with a
@patchdecorator should expand the@detailssection to explain the effect of the patch.- Follow the Doxygen documentation rules for the
@detailssection when expanding.
- Follow the Doxygen documentation rules for the
- Test cases have an additional tag
@testin their Doxygen documentation.- The
@testtag describes if this is a happy path, edge case, error case, or corner case. - The
@testline is placed after the blank line following whatever is last of:- a
@detailsblock - a
@noteblock - a code example using the
@code{.text}and@endcodetags
- a
- The
@testtag is followed by a blank line.
- The
- Test files may have a class level
setUpClassmethod for initializing test fixtures, with a@classmethoddecorator. - Test files may have a class level
tearDownClassmethod for cleaning up test fixtures, with a@classmethoddecorator. - Test files may have a
tearDownmethod for cleaning up individual test cases. - Test cases are named
test_<functionality>, where<functionality>describes the specific feature or behavior being tested. - Test cases using mock objects have an appropriate
@patchdecorator for the mock in the test case.
Casing Rules
- Use
snake_casefor public file names, methods, variables, and module names. - Use single-leading-underscore
snake_casefor non-public helper methods. - Use double-leading-underscore
snake_case- only for class methods that intentionally rely on Python name mangling to avoid accidental override or external access.
- Use leading-underscore
snake_casefor non-public module, class, and instance members; for example,_private_variableor_private_method. - Use
PascalCasefor classes. - USE
ALL_CAPSfor constants.
