From 3660dbea01a257fbdb5e5f6b96b3ac1bed11e714 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Tue, 6 May 2025 15:30:16 +0200 Subject: [PATCH 01/33] Initial repo setup. Migrated code for sharing from the collector action. --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 25 + .github/ISSUE_TEMPLATE/feature_request.md | 21 + .github/ISSUE_TEMPLATE/question.md | 13 + .github/ISSUE_TEMPLATE/spike_task.md | 36 + .github/pull_request_template.md | 6 + .github/workflows/check_pr_release_notes.yml | 39 ++ .gitignore | 5 +- .pylintrc | 647 ++++++++++++++++++ CONTRIBUTING.md | 36 + DEVELOPER.md | 242 +++++++ README.md | 2 + pyproject.toml | 23 + requirements.txt | 12 + src/__init__.py | 15 + src/living_doc_utilities/__init__.py | 0 src/living_doc_utilities/constants.py | 29 + src/living_doc_utilities/decorators.py | 84 +++ src/living_doc_utilities/github/__init__.py | 0 .../github/rate_limiter.py | 85 +++ src/living_doc_utilities/github/utils.py | 46 ++ src/living_doc_utilities/inputs/__init__.py | 0 .../inputs/action_inputs.py | 70 ++ src/living_doc_utilities/logging_config.py | 51 ++ src/living_doc_utilities/model/__init__.py | 0 src/living_doc_utilities/model/issue.py | 137 ++++ src/living_doc_utilities/model/issues.py | 83 +++ .../model/project_status.py | 106 +++ 28 files changed, 1813 insertions(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/ISSUE_TEMPLATE/spike_task.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/check_pr_release_notes.yml create mode 100644 .pylintrc create mode 100644 CONTRIBUTING.md create mode 100644 DEVELOPER.md create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 src/living_doc_utilities/__init__.py create mode 100644 src/living_doc_utilities/constants.py create mode 100644 src/living_doc_utilities/decorators.py create mode 100644 src/living_doc_utilities/github/__init__.py create mode 100644 src/living_doc_utilities/github/rate_limiter.py create mode 100644 src/living_doc_utilities/github/utils.py create mode 100644 src/living_doc_utilities/inputs/__init__.py create mode 100644 src/living_doc_utilities/inputs/action_inputs.py create mode 100644 src/living_doc_utilities/logging_config.py create mode 100644 src/living_doc_utilities/model/__init__.py create mode 100644 src/living_doc_utilities/model/issue.py create mode 100644 src/living_doc_utilities/model/issues.py create mode 100644 src/living_doc_utilities/model/project_status.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..94e8784 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @miroslavpojer @Zejnilovic @OlivieFranklova diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3aa610b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: Create a report to help us improve +labels: 'bug' + +--- + +## Describe the bug +A clear and concise description of what the bug is. + +## To Reproduce +Steps to reproduce the behavior OR commands run: +1. Go to '...' +2. Click on '....' +3. Enter value '...' +4. See error + +## Expected behavior +A clear and concise description of what you expected to happen. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Additional context +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..74a56c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: 'enhancement' + +--- + +## Background +A clear and concise description of where the limitation lies. + +## Feature +A description of the requested feature. + +## Example [Optional] +A simple example if applicable. + +## Proposed Solution [Optional] +Solution Ideas: +1. +2. +3. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..24ba89d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,13 @@ +--- +name: Question +about: Ask a question +labels: 'question' + +--- + +## Background [Optional] +A clear explanation of the reason for raising the question. +This gives us a better understanding of your use cases and how we might accommodate them. + +## Question +A clear and concise inquiry diff --git a/.github/ISSUE_TEMPLATE/spike_task.md b/.github/ISSUE_TEMPLATE/spike_task.md new file mode 100644 index 0000000..d071e81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/spike_task.md @@ -0,0 +1,36 @@ +--- +name: Spike +about: Issue template for spikes, research and investigation tasks +labels: 'spike' + +--- + +## Background +A clear and concise description of the problem or a topic we need to understand. + +Feel free to add information about why it's needed and what assumptions you have at the moment. + +## Questions To Answer + +1. +2. +3. + +## Desired Outcome + +The list of desired outcomes of this spike ticket. + +```[tasklist] +### Tasks +- [ ] Questions have been answered or we have a clearer idea of how to get to our goal +- [ ] Discussion with the team +- [ ] Documentation +- [ ] Create recommendations and new implementation tickets +- [ ] item here.. +``` + +## Additional Info/Resources [Optional] + +1. +2. +3. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..2b2b47e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,6 @@ +> Add information about goal of Pull Request. + +--- +> **Note:** Remember to link this PR to the related issue by adding `Closes #issue_number` to the description above. + +Closes #issue_number diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml new file mode 100644 index 0000000..3f2d6d6 --- /dev/null +++ b/.github/workflows/check_pr_release_notes.yml @@ -0,0 +1,39 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Check PR Release Notes in Description + +on: + pull_request: + types: [opened, synchronize, reopened, edited, labeled, unlabeled] + branches: [ master ] + +jobs: + check-release-notes: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Check presence of release notes in PR description + uses: AbsaOSS/release-notes-presence-check@v0.3.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-repository: ${{ github.repository }} + pr-number: ${{ github.event.number }} diff --git a/.gitignore b/.gitignore index 0a19790..1b0a35c 100644 --- a/.gitignore +++ b/.gitignore @@ -165,10 +165,13 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # Ruff stuff: .ruff_cache/ # PyPI configuration file .pypirc + +run_script.sh +output/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..cdc470b --- /dev/null +++ b/.pylintrc @@ -0,0 +1,647 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.11 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + +[MASTER] + +ignore-paths=tests + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=5 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..936e584 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# How to Contribute? + +## **Identifying and Reporting Bugs** +* **Ensure the bug has not already been reported** by searching our **[GitHub Issues](https://github.com/AbsaOSS/living-doc-utilities/issues)**. +* If you cannot find an open issue describing the problem, use the **Bug report** template to open a new one. Tag it with the **bug** label. + +## **Proposing New Features** + +* **Check if the feature has already been requested** by searching through our **[GitHub Issues](https://github.com/AbsaOSS/living-doc-utilities/issues)**. +* If the feature request doesn't exist, feel free to create a new one. Tag it with the **request** label. + +## **Contributing to Development** + +* Check _Issues_ logs for the desired feature or bug. Ensure that no one else is already working on it. + * If the feature/bug is not yet filed, please create a detailed issue first: + * **"Detail Your Idea or Issue"** +* Fork the repository. +* Begin coding. Feel free to ask questions and collaborate with us. + * Commit messages should reference the GitHub Issue and provide a concise description: + * **"#34 - Implement Feature X"** + * Remember to include tests for your code. +* Once done, push to your fork and submit a Pull Request to our `master` branch: + * Pull Request titles should begin with the GitHub Issue number: + * **"45 - Implementing New Analytics Feature"** + * Ensure the Pull Request description clearly outlines your solution. + * Link your PR to the relevant _Issue_. + +### Community and Communication + +If you have any questions or need help, don't hesitate to reach out through our GitHub discussion section. We're here to help! + +#### Thanks! + +Your contributions are invaluable to us. Thank you for being part of the AbsaOSS community and helping us grow and improve! + +The AbsaOSS Team diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 0000000..6125b19 --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,242 @@ +# Living Documentation Utilities - for Developers + +- [Project Setup](#project-setup) +- [Run Scripts Locally](#run-scripts-locally) +- [Run Pylint Check Locally](#run-pylint-check-locally) +- [Run Black Tool Locally](#run-black-tool-locally) +- [Run mypy Tool Locally](#run-mypy-tool-locally) +- [Run Unit Test](#run-unit-test) +- [Code Coverage](#code-coverage) +- [Releasing](#releasing) + +## Project Setup + +If you need to build the action locally, follow these steps for project setup: + +### Prepare the Environment + +```shell +python3 --version +``` + +### Set Up Python Environment + +```shell +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +--- +## Run Scripts Locally + +If you need to run the scripts locally, follow these steps: + +### Create the Shell Script + +Create the shell file in the root directory. We will use `run_script.sh`. +```shell +touch run_script.sh +``` +Add the shebang line at the top of the sh script file. +``` +#!/bin/sh +``` + +### Set the Environment Variables + +Set the configuration environment variables in the shell script following the structure below. +The generator supports mining in multiple modes, so you can use just the environment variables you need. +Also make sure that the INPUT_GITHUB_TOKEN is configured in your environment variables. +``` +# Essential environment variables for GitHub Action functionality +export INPUT_GITHUB_TOKEN=$(printenv GITHUB_TOKEN) +export INPUT_VERBOSE_LOGGING=true +``` + +### Running the script locally + +For running the GitHub action locally, incorporate these commands into the shell script and save it. +``` +python3 main.py +``` +The whole script should look like this example: +``` +#!/bin/sh + +# Essential environment variables for GitHub Action functionality +export INPUT_GITHUB_TOKEN=$(printenv GITHUB_TOKEN) +export INPUT_VERBOSE_LOGGING=true + +python3 main.py +``` + +### Make the Script Executable + +From the terminal, at the root of this project, make the script executable: +```shell +chmod +x run_script.sh +``` + +### Run the Script + +```shell +./run_script.sh +``` + +--- +## Run Pylint Check Locally + +This project uses the [Pylint](https://pypi.org/project/pylint/) tool for static code analysis. +Pylint analyses your code without actually running it. +It checks for errors, enforces coding standards, looks for code smells, etc. +We do exclude the `tests/` file from the Pylint check. + +Pylint displays a global evaluation score for the code, rated out of a maximum score of 10.0. +We are aiming to keep our code quality high above the score 9.5. + +Follow these steps to run Pylint check locally: + +- Perform the [setup of python venv](#set-up-python-environment). + +### Run Pylint + +Run Pylint on all files that are currently tracked by Git in the project. +```shell +pylint $(git ls-files '*.py') +``` + +To run Pylint on a specific file, follow the pattern `pylint /.py`. + +Example: +```shell +pylint serde/TODO.py +``` + +### Expected Output + +This is an example of the expected console output after running the tool: +``` +************* Module main +main.py:30:0: C0116: Missing function or method docstring (missing-function-docstring) + +------------------------------------------------------------------ +Your code has been rated at 9.41/10 (previous run: 8.82/10, +0.59) +``` + +--- +## Run Black Tool Locally + +This project uses the [Black](https://github.com/psf/black) tool for code formatting. +Black aims for consistency, generality, readability and reducing git diffs. +The coding style used can be viewed as a strict subset of PEP 8. + +The root project file `pyproject.toml` defines the Black tool configuration. +In this project we are accept a line length of 120 characters. +We also exclude the `tests/` files from black formatting. + +Follow these steps to format your code with Black locally: + +- Perform the [setup of python venv](#set-up-python-environment). + +### Run Black + +Run Black on all files that are currently tracked by Git in the project. +```shell +black $(git ls-files '*.py') +``` + +To run Black on a specific file, follow the pattern `black /.py`. + +Example: +```shell +black serde/TODO.py +``` + +### Expected Output + +This is an example of the expected console output after running the tool: +``` +All done! ✨ 🍰 ✨ +1 file reformatted. +``` + +--- + +## Run mypy Tool Locally + +This project uses the [my[py]](https://mypy.readthedocs.io/en/stable/) +tool which is a static type checker for Python. + +> Type checkers help ensure that you’re using variables and functions in your code correctly. +> With mypy, add type hints (PEP 484) to your Python programs, +> and mypy will warn you when you use those types incorrectly. + +my[py] configuration is in `pyptoject.toml` file. + +Follow these steps to format your code with my[py] locally: + +### Run my[py] + +Run my[py] on all files in the project. +```shell + mypy . +``` + +To run my[py] check on a specific file, follow the pattern `mypy /.py --check-untyped-defs`. + +Example: +```shell + mypy serde/TODO.py +``` + +### Expected Output + +This is an example of the expected console output after running the tool: +``` +Success: no issues found in 1 source file +``` + +--- + + +## Run Unit Test + +Unit tests are written using the Pytest framework. To run all the tests, use the following command: +```shell +pytest --ignore=tests/integration tests/ +``` + +You can modify the directory to control the level of detail or granularity as per your needs. + +To run a specific test, run the command following the pattern below: +```shell +pytest tests/utils/test_utils.py::test_make_issue_key +``` + +--- +## Code Coverage + +This project uses the [pytest-cov](https://pypi.org/project/pytest-cov/) plugin to generate test coverage reports. +The objective of the project is to achieve a minimum score of 80 %. We do exclude the `tests/` file from the coverage report. + +To generate the coverage report, run the following command: +```shell +pytest --ignore=tests/integration --cov=. tests/ --cov-fail-under=80 --cov-report=html +``` + +See the coverage report on the path: + +```shell +open htmlcov/index.html +``` + +--- +## Releasing + +This project uses GitHub Actions for deployment draft creation. The deployment process is semi-automated by a workflow defined in `.github/workflows/release_draft.yml`. + +- **Trigger the workflow**: The `release_draft.yml` workflow is triggered on workflow_dispatch. +- **Create a new draft release**: The workflow creates a new draft release in the repository. +- **Finalize the release draft**: Edit the draft release to add a title, description, and any other necessary details related to the GitHub Action. +- **Publish the release**: Once the draft is ready, publish the release to make it publicly available. diff --git a/README.md b/README.md index 51a2d3e..89de460 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # living-doc-utilities Core utility functions and data models shared across the living-doc ecosystem. Provides structured model classes, reusable transformation logic, and serialization/deserialization (serde) utilities to support data exchange between components. + +TODO - provide content diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3d28ab7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "living-doc-utilities" +version = "0.1.0" +description = "Core utility functions and data models shared across the living-doc ecosystem." +authors = [{ name="Mirosva Pojer", email="miroslav.pojer@absa.africa" }] +readme = "README.md" +license = {text = "Apache 2.0"} +requires-python = ">=3.12" + +[project.urls] +Repository = "https://github.com/AbsaOSS/living-doc-utilities" + +[tool.black] +line-length = 120 +target-version = ['py312'] +force-exclude = '''test''' + +[tool.coverage.run] +omit = ["tests/*"] + +[tool.mypy] +check_untyped_defs = true +exclude = "tests" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c98affe --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +mypy==1.15.0 +mypy-extensions==1.0.0 +requests==2.31.0 +typing_extensions==4.12.2 +PyGithub==2.3.0 +pylint==3.2.6 +black==24.8.0 +pytest==7.4.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +types-requests==2.32.0.20250328 + diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..f7115cb --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/src/living_doc_utilities/__init__.py b/src/living_doc_utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/living_doc_utilities/constants.py b/src/living_doc_utilities/constants.py new file mode 100644 index 0000000..41aea89 --- /dev/null +++ b/src/living_doc_utilities/constants.py @@ -0,0 +1,29 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains all constants and enums used across the project. +""" +from enum import Enum + +# General Action inputs +GITHUB_TOKEN = "GITHUB_TOKEN" + +# Output related +OUTPUT_PATH = "./output" + +# Symbol, when no project is attached to an issue +NO_PROJECT_DATA = "---" diff --git a/src/living_doc_utilities/decorators.py b/src/living_doc_utilities/decorators.py new file mode 100644 index 0000000..bde0a13 --- /dev/null +++ b/src/living_doc_utilities/decorators.py @@ -0,0 +1,84 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains decorators for adding debug logging to method calls +and for creating rate-limited safe call functions. +""" + +import logging + +from typing import Callable, Optional, Any +from functools import wraps +from github import GithubException +from requests import Timeout, RequestException + +from src.living_doc_utilities.github.rate_limiter import GithubRateLimiter + +logger = logging.getLogger(__name__) + + +def debug_log_decorator(method: Callable) -> Callable: + """ + Decorator to add debug logging for a method call. + + @param method: The method to decorate. + @return: The decorated method. + """ + + @wraps(method) + def wrapped(*args, **kwargs) -> Optional[Any]: + logger.debug("Calling method %s with args: %s and kwargs: %s.", method.__name__, args, kwargs) + result = method(*args, **kwargs) + logger.debug("Method %s returned %s.", method.__name__, result) + return result + + return wrapped + + +def safe_call_decorator(rate_limiter: GithubRateLimiter) -> Callable: + """ + Decorator factory to create a rate-limited safe call function. + + @param rate_limiter: The rate limiter to use. + @return: The decorator. + """ + + def decorator(method: Callable) -> Callable: + # Note: Keep the log decorator first to log the correct method name. + @debug_log_decorator + @wraps(method) + @rate_limiter + def wrapped(*args, **kwargs) -> Optional[Any]: + try: + return method(*args, **kwargs) + except (ConnectionError, Timeout) as e: + logger.error("Network error calling %s: %s.", method.__name__, e, exc_info=True) + return None + except GithubException as e: + logger.error("GitHub API error calling %s: %s.", method.__name__, e, exc_info=True) + return None + except RequestException as e: + logger.error("HTTP error calling %s: %s.", method.__name__, e, exc_info=True) + return None + # pylint: disable=broad-exception-caught + except Exception as e: + logger.error("%s by calling %s: %s.", type(e).__name__, method.__name__, e, exc_info=True) + return None + + return wrapped + + return decorator diff --git a/src/living_doc_utilities/github/__init__.py b/src/living_doc_utilities/github/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/living_doc_utilities/github/rate_limiter.py b/src/living_doc_utilities/github/rate_limiter.py new file mode 100644 index 0000000..f7cc00b --- /dev/null +++ b/src/living_doc_utilities/github/rate_limiter.py @@ -0,0 +1,85 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains a GitHub Rate Limiter class methods, +which acts as a rate limiter for GitHub API calls. +""" + +import logging +import time +from datetime import datetime +from typing import Callable, Optional, Any +from github import Github + +logger = logging.getLogger(__name__) + + +# pylint: disable=too-few-public-methods +# It is fine to have a single method in this class, since we use it as a callable class +class GithubRateLimiter: + """ + A class that acts as a rate limiter for GitHub API calls. + + Note: + This class is used as a callable class, hence the `__call__` method. + """ + + def __init__(self, github_client: Github): + self.__github_client: Github = github_client + + @property + def github_client(self) -> Github: + """Getter of the GitHub client.""" + return self.__github_client + + def __call__(self, method: Callable) -> Callable: + """ + Wraps the provided method to ensure it respects the GitHub API rate limit. + + @param method: The method to wrap. + @return: The wrapped method. + """ + + def wrapped_method(*args, **kwargs) -> Optional[Any]: + rate_limit = self.github_client.get_rate_limit().core + remaining_calls = rate_limit.remaining + reset_time = rate_limit.reset.timestamp() + + if remaining_calls < 5: + logger.info("Rate limit almost reached. Sleeping until reset time.") + sleep_time = reset_time - (now := time.time()) + while sleep_time <= 0: + # Note: received values can be in the past, so the time shift to 1st positive value is needed + reset_time += 3600 # Add 1 hour in seconds + sleep_time = reset_time - now + + total_sleep_time = sleep_time + 5 # Total sleep time including the additional 5 seconds + hours, remainder = divmod(total_sleep_time, 3600) + minutes, seconds = divmod(remainder, 60) + + logger.info( + "Sleeping for %s hours, %s minutes, and %s seconds until %s.", + int(hours), + int(minutes), + int(seconds), + datetime.fromtimestamp(reset_time).strftime("%Y-%m-%d %H:%M:%S"), + ) + time.sleep(sleep_time + 5) # Sleep for the calculated time plus 5 seconds + + return method(*args, **kwargs) + + return wrapped_method diff --git a/src/living_doc_utilities/github/utils.py b/src/living_doc_utilities/github/utils.py new file mode 100644 index 0000000..c4c5ed3 --- /dev/null +++ b/src/living_doc_utilities/github/utils.py @@ -0,0 +1,46 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import logging + +logger = logging.getLogger(__name__) + + +def get_action_input(name: str, default: str = "") -> str: + """ + Get the input value from the environment variables. + + @param name: The name of the input parameter. + @param default: The default value to return if the environment variable is not set. + @return: The value of the specified input parameter, or an empty string + """ + return os.getenv(f'INPUT_{name.replace("-", "_").upper()}', default=default) + + +def set_action_output(name: str, value: str, default_output_path: str = "default_output.txt") -> None: + """ + Write an action output to a file in the format expected by GitHub Actions. + + This function writes the output in a specific format that includes the name of the + output and its value. The output is appended to the specified file. + + @param name: The name of the output parameter. + @param value: The value of the output parameter. + @param default_output_path: The default file path to which the output is written if the + @return: None + """ + output_file = os.getenv("GITHUB_OUTPUT", default_output_path) + with open(output_file, "a", encoding="utf-8") as f: + f.write(f"{name}={value}\n") diff --git a/src/living_doc_utilities/inputs/__init__.py b/src/living_doc_utilities/inputs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/living_doc_utilities/inputs/action_inputs.py b/src/living_doc_utilities/inputs/action_inputs.py new file mode 100644 index 0000000..79497f6 --- /dev/null +++ b/src/living_doc_utilities/inputs/action_inputs.py @@ -0,0 +1,70 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains an Action Inputs class methods, +which are essential for running the GH action. +""" +import logging +from abc import ABC + +from src.living_doc_utilities.constants import GITHUB_TOKEN +from src.living_doc_utilities.github.utils import get_action_input + +logger = logging.getLogger(__name__) + + +class BaseActionInputs(ABC): + """ + A class representing all the action inputs. It is responsible for loading, managing + and validating the inputs required for running the GH Action. + """ + + @staticmethod + def get_github_token() -> str: + """ + Getter of the GitHub authorization token. + @return: The GitHub authorization token. + """ + return get_action_input(GITHUB_TOKEN) + + def validate_user_configuration(self) -> bool: + """ + Checks that all the user configurations defined are correct. + @return: True if the configuration is correct, False otherwise. + """ + logger.debug("User configuration validation started") + repository_error_count = self._validate() + if repository_error_count > 0: + logger.debug("User configuration validation failed.") + return False + + logger.debug("User configuration validation successfully completed.") + return True + + def _validate(self) -> int: + ... + + def print_effective_configuration(self) -> None: + """ + Prints the effective configuration of the action inputs. + """ + logger.debug("Effective configuration:") + logger.debug("GitHub token: %s", self.get_github_token()) + self._print_effective_configuration() + + def _print_effective_configuration(self) -> None: + ... diff --git a/src/living_doc_utilities/logging_config.py b/src/living_doc_utilities/logging_config.py new file mode 100644 index 0000000..67b267f --- /dev/null +++ b/src/living_doc_utilities/logging_config.py @@ -0,0 +1,51 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains a method to set up logging in the project. +""" + +import logging +import os +import sys + + +def setup_logging() -> None: + """ + Set up the logging configuration in the project + + @return: None + """ + # Load logging configuration from the environment variables + is_verbose_logging: bool = os.getenv("INPUT_VERBOSE_LOGGING", "false").lower() == "true" + is_debug_mode = os.getenv("RUNNER_DEBUG", "0") == "1" + level = logging.DEBUG if is_verbose_logging or is_debug_mode else logging.INFO + + # Set up the logging configuration + logging.basicConfig( + level=level, + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + handlers=[logging.StreamHandler(sys.stdout)], + ) + sys.stdout.flush() + + logging.info("Logging configuration set up.") + + if is_verbose_logging: + logging.debug("Verbose logging enabled.") + if is_debug_mode: + logging.debug("Debug mode enabled by CI runner.") diff --git a/src/living_doc_utilities/model/__init__.py b/src/living_doc_utilities/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/living_doc_utilities/model/issue.py b/src/living_doc_utilities/model/issue.py new file mode 100644 index 0000000..bb00b39 --- /dev/null +++ b/src/living_doc_utilities/model/issue.py @@ -0,0 +1,137 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains the Issue class, which represents the data of an issue. +""" + +from typing import Any, Optional + +from src.living_doc_utilities.model.project_status import ProjectStatus + + +# pylint: disable=too-many-instance-attributes +class Issue: + """ + Represents an issue in the GitHub repository ecosystem. + """ + + STATE = "state" + REPOSITORY_ID = "repository_id" + TITLE = "title" + NUMBER = "number" + CREATED_AT = "created_at" + UPDATED_AT = "updated_at" + CLOSED_AT = "closed_at" + HTML_URL = "html_url" + BODY = "body" + LABELS = "labels" + LINKED_TO_PROJECT = "linked_to_project" + PROJECT_STATUS = "project_status" + + def __init__(self, repository_id: str, title: str, number: int): + self.repository_id: str = repository_id + self.title: str = title + self.issue_number: int = number + + # issue's properties + self.state: Optional[str] = None + self.created_at: Optional[str] = None + self.updated_at: Optional[str] = None + self.closed_at: Optional[str] = None + self.html_url: Optional[str] = None + self.body: Optional[str] = None + self.labels: Optional[list[str]] = None + + # GitHub Projects related properties + self.linked_to_project: Optional[bool] = None + self.project_statuses: Optional[list[ProjectStatus]] = None + + def to_dict(self) -> dict[str, Any]: + """ + Converts the issue an object to a dictionary representation. + + @return: Dictionary representation of the issue. + """ + res: dict[str, Any] = { + "repository_id": self.repository_id, + "title": self.title, + "number": self.issue_number, + } + + if self.state: + res[self.STATE] = self.state + if self.created_at: + res[self.CREATED_AT] = self.created_at + if self.updated_at: + res[self.UPDATED_AT] = self.updated_at + if self.closed_at: + res[self.CLOSED_AT] = self.closed_at + if self.html_url: + res[self.HTML_URL] = self.html_url + if self.body: + res[self.BODY] = self.body + if self.labels: + res[self.LABELS] = self.labels + if self.project_statuses: + res[self.PROJECT_STATUS] = [project_status.to_dict() for project_status in self.project_statuses] + + res[self.LINKED_TO_PROJECT] = self.linked_to_project if self.linked_to_project is not None else False + + return res + + def organization_name(self) -> str: + """ + Extracts the organization name from the repository ID. + """ + return self.repository_id.split("/")[0] + + def repository_name(self) -> str: + """ + Extracts the repository name from the repository ID. + """ + return self.repository_id.split("/")[1] + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "Issue": + """ + Creates an Issue object from a dictionary representation. + + @param data: Dictionary representation of the issue. + @return: Issue object. + """ + issue: Issue = cls( + repository_id=data[cls.REPOSITORY_ID], + title=data[cls.TITLE], + number=data[cls.NUMBER], + ) + + issue.state = data.get(cls.STATE, None) + issue.created_at = data.get(cls.CREATED_AT, None) + issue.updated_at = data.get(cls.UPDATED_AT, None) + issue.closed_at = data.get(cls.CLOSED_AT, None) + issue.html_url = data.get(cls.HTML_URL, None) + issue.body = data.get(cls.BODY, None) + issue.labels = data.get(cls.LABELS, None) + issue.linked_to_project = data.get(cls.LINKED_TO_PROJECT, None) + + project_statuses_data = data.get(cls.PROJECT_STATUS, None) + if project_statuses_data and isinstance(project_statuses_data, list): + issue.project_statuses = [ProjectStatus.from_dict(status_data) for status_data in project_statuses_data] + else: + issue.project_statuses = None + + return issue diff --git a/src/living_doc_utilities/model/issues.py b/src/living_doc_utilities/model/issues.py new file mode 100644 index 0000000..6326f03 --- /dev/null +++ b/src/living_doc_utilities/model/issues.py @@ -0,0 +1,83 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains the Issues class, which is used to manage issues in the GitHub repository ecosystem. +""" + +import json +from pathlib import Path +from typing import Optional + +from src.living_doc_utilities.model.issue import Issue + + +class Issues: + """ + This class represents a collection of issues in a GitHub repository ecosystem. + """ + + def __init__(self, issues: Optional[dict[str, Issue]] = None) -> None: + self.issues: dict[str, Issue] = issues or {} + + def save_to_json(self, file_path: str | Path) -> None: + """ + Save the issues to a JSON file. + + @param file_path: Path to the JSON file. + @return: None + """ + data = {key: ci.to_dict() for key, ci in self.issues.items()} + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + + @classmethod + def load_from_json(cls, file_path: str | Path) -> "Issues": + """ + Load issues from a JSON file. + + @param file_path: Path to the JSON file. + @return: Issues object. + """ + with open(file_path, "r", encoding="utf-8") as f: + data = json.load(f) + + issues = {key: Issue.from_dict(value) for key, value in data.items()} + return cls(issues) + + def add_issue(self, key: str, issue: Issue) -> None: + self.issues[key] = issue + + def get_issue(self, key: str) -> Issue: + return self.issues[key] + + def all_issues(self) -> dict[str, Issue]: + return self.issues + + def count(self) -> int: + return len(self.issues) + + @staticmethod + def make_issue_key(organization_name: str, repository_name: str, issue_number: int) -> str: + """ + Create a unique string key to identify the issue. + + @param organization_name: The name of the organization where the issue is located at. + @param repository_name: The name of the repository where the issue is located at. + @param issue_number: The number of the issue. + @return: The unique string key for the issue. + """ + return f"{organization_name}/{repository_name}/{issue_number}" diff --git a/src/living_doc_utilities/model/project_status.py b/src/living_doc_utilities/model/project_status.py new file mode 100644 index 0000000..524c8c8 --- /dev/null +++ b/src/living_doc_utilities/model/project_status.py @@ -0,0 +1,106 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains a data container for an issue's Project Status. +""" +from src.living_doc_utilities.constants import NO_PROJECT_DATA + + +# pylint: disable=too-many-instance-attributes +class ProjectStatus: + """ + A class representing the project status of an issue is responsible for access + and change project issue status specifics. + """ + + def __init__(self): + self.__project_title: str = NO_PROJECT_DATA + self.__status: str = NO_PROJECT_DATA + self.__priority: str = NO_PROJECT_DATA + self.__size: str = NO_PROJECT_DATA + self.__moscow: str = NO_PROJECT_DATA + + @property + def project_title(self) -> str: + """Getter of the issue attached project title.""" + return self.__project_title + + @project_title.setter + def project_title(self, value: str): + self.__project_title = value + + @property + def status(self) -> str: + """Getter of the issue project status.""" + return self.__status + + @status.setter + def status(self, value: str): + self.__status = value + + @property + def priority(self) -> str: + """Getter of the issue project priority.""" + return self.__priority + + @priority.setter + def priority(self, value: str): + self.__priority = value + + @property + def size(self) -> str: + """Getter of the issue project difficulty.""" + return self.__size + + @size.setter + def size(self, value: str): + self.__size = value + + @property + def moscow(self) -> str: + """Getter of the issue project MoSCoW prioritization.""" + return self.__moscow + + @moscow.setter + def moscow(self, value: str): + self.__moscow = value + + def to_dict(self) -> dict: + """ + Converts the ProjectStatus object to a dictionary. + """ + return { + "project_title": self.__project_title, + "status": self.__status, + "priority": self.__priority, + "size": self.__size, + "moscow": self.__moscow, + } + + @classmethod + def from_dict(cls, data: dict) -> "ProjectStatus": + """ + Populates the ProjectStatus object from a dictionary. + """ + res = ProjectStatus() + res.project_title = data.get("project_title", NO_PROJECT_DATA) + res.status = data.get("status", NO_PROJECT_DATA) + res.priority = data.get("priority", NO_PROJECT_DATA) + res.size = data.get("size", NO_PROJECT_DATA) + res.moscow = data.get("moscow", NO_PROJECT_DATA) + + return res From e4083be8aec44c307981b3cbe833ecb2c29b528d Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 08:45:20 +0200 Subject: [PATCH 02/33] Update of DEVELOPER.md file. Check solo file commands. --- DEVELOPER.md | 65 ++----------------- .../inputs/action_inputs.py | 6 +- 2 files changed, 6 insertions(+), 65 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index 6125b19..0f2dde9 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1,7 +1,6 @@ # Living Documentation Utilities - for Developers - [Project Setup](#project-setup) -- [Run Scripts Locally](#run-scripts-locally) - [Run Pylint Check Locally](#run-pylint-check-locally) - [Run Black Tool Locally](#run-black-tool-locally) - [Run mypy Tool Locally](#run-mypy-tool-locally) @@ -26,64 +25,6 @@ python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` - ---- -## Run Scripts Locally - -If you need to run the scripts locally, follow these steps: - -### Create the Shell Script - -Create the shell file in the root directory. We will use `run_script.sh`. -```shell -touch run_script.sh -``` -Add the shebang line at the top of the sh script file. -``` -#!/bin/sh -``` - -### Set the Environment Variables - -Set the configuration environment variables in the shell script following the structure below. -The generator supports mining in multiple modes, so you can use just the environment variables you need. -Also make sure that the INPUT_GITHUB_TOKEN is configured in your environment variables. -``` -# Essential environment variables for GitHub Action functionality -export INPUT_GITHUB_TOKEN=$(printenv GITHUB_TOKEN) -export INPUT_VERBOSE_LOGGING=true -``` - -### Running the script locally - -For running the GitHub action locally, incorporate these commands into the shell script and save it. -``` -python3 main.py -``` -The whole script should look like this example: -``` -#!/bin/sh - -# Essential environment variables for GitHub Action functionality -export INPUT_GITHUB_TOKEN=$(printenv GITHUB_TOKEN) -export INPUT_VERBOSE_LOGGING=true - -python3 main.py -``` - -### Make the Script Executable - -From the terminal, at the root of this project, make the script executable: -```shell -chmod +x run_script.sh -``` - -### Run the Script - -```shell -./run_script.sh -``` - --- ## Run Pylint Check Locally @@ -110,7 +51,7 @@ To run Pylint on a specific file, follow the pattern `pylint // bool: logger.debug("User configuration validation successfully completed.") return True - def _validate(self) -> int: - ... + def _validate(self) -> int: ... def print_effective_configuration(self) -> None: """ @@ -66,5 +65,4 @@ def print_effective_configuration(self) -> None: logger.debug("GitHub token: %s", self.get_github_token()) self._print_effective_configuration() - def _print_effective_configuration(self) -> None: - ... + def _print_effective_configuration(self) -> None: ... From c3d0e74172cf584d46056ed3423065948da2b296 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 09:53:08 +0200 Subject: [PATCH 03/33] Update README.md and DEVELOPER.md. --- DEVELOPER.md | 77 +++++++++++++++++++++++++++++++++++--- README.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 171 insertions(+), 9 deletions(-) diff --git a/DEVELOPER.md b/DEVELOPER.md index 0f2dde9..fa30500 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -175,11 +175,76 @@ open htmlcov/index.html --- ## Releasing -This project uses GitHub Actions for deployment draft creation. The deployment process is semi-automated by a workflow defined in `.github/workflows/release_draft.yml`. +### 1. One-Time Setup -TODO +#### Register on PyPI -- **Trigger the workflow**: The `release_draft.yml` workflow is triggered on workflow_dispatch. -- **Create a new draft release**: The workflow creates a new draft release in the repository. -- **Finalize the release draft**: Edit the draft release to add a title, description, and any other necessary details related to the GitHub Action. -- **Publish the release**: Once the draft is ready, publish the release to make it publicly available. +- Create an account on https://pypi.org +- (Optional) Also register on TestPyPI for safe dry runs + +#### Prepare Project for Build + +Ensure your pyproject.toml includes this structure: + +```toml +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "living-doc-utilities" +version = "0.1.0" +description = "Shared utility functions and data models for the Living Documentation ecosystem." +authors = [{ name = "Miroslav Pojer", email = "miroslav.pojer@absa.africa" }] +readme = "README.md" +license = { text = "Apache-2.0" } +requires-python = ">=3.12" +dependencies = [] + +[tool.setuptools.packages.find] +where = ["src"] +``` +### 2. Build the Package +Install the required tools: + +```bash +pip install build twine +``` + +Then build the package: + +```bash +python -m build +``` + +This creates a dist/ folder with .whl and .tar.gz artifacts. + +### 3. Upload to PyPI + +Use twine to securely upload the package: + +```bash +twine upload dist/* +``` + +You’ll be prompted for your PyPI username and password (or use an API token as the password). + +To test the process before pushing live, use TestPyPI: + +```bash +twine upload --repository testpypi dist/* +``` + +### 4. Test Installation (optional) + +Test from PyPI: + +```bash +Test from PyPI: +``` + +Or from TestPyPI: + +```bash +pip install --index-url https://test.pypi.org/simple/ living-doc-utilities +``` diff --git a/README.md b/README.md index 89de460..55c9059 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,101 @@ -# living-doc-utilities -Core utility functions and data models shared across the living-doc ecosystem. Provides structured model classes, reusable transformation logic, and serialization/deserialization (serde) utilities to support data exchange between components. +# Living Documentation Utilities -TODO - provide content +- [Motivation](#motivation) +- [Usage](#usage) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Option 1: Local Development (editable mode)](#option-1-local-development-editable-mode) + - [Option 2: From GitHub (using a release tag)](#option-2-from-github-using-a-release-tag) + - [Option 3: From PyPI](#option-3-from-pypi) +- [Developer Guide](#developer-guide) +- [Contribution Guidelines](#contribution-guidelines) + - [How to Contribute](#how-to-contribute) + - [License Information](#license-information) + - [Contact or Support Information](#contact-or-support-information) + + +## Motivation + +The `living-doc-utilities` library contains core utility functions and shared data models used across the Living Documentation GitHub Actions ecosystem. +It provides: + +- Reusable transformation and helper logic +- Serialization and deserialization (serde) utilities +- Common structured data models for consistent cross-action communication +- A foundation for expanding shared functionality in the future + +It is designed to reduce duplication, improve testability, and simplify maintenance across the ecosystem. + + +--- +## Usage + +### Prerequisites + +Before installing this library, ensure you have: + +- Python 3.12 or later +- pip package installer +- (Recommended) Virtual environment setup in your project + +### Installation + +You can install the utilities locally or directly from GitHub. + +#### Option 1: Local Development (editable mode) + +If you are developing the library alongside another project: + +```bash +pip install -e ../living-doc-utilities +``` + +Make sure you activate the virtual environment in your main project before installing. + +#### Option 2: From GitHub (using a release tag) + +```bash +pip install git+https://github.com/AbsaOSS/living-doc-utilities@v0.1.0 +``` + +#### Option 3: From PyPI + +Once published to PyPI, simply run: + +```bash +pip install living-doc-utilities +``` + +To pin a specific version: + +```bash +pip install living-doc-utilities==0.1.0 +``` + +--- + +## Developer Guide + +See this [Developer Guide](DEVELOPER.md) for more technical, development-related information. + +--- +## Contribution Guidelines + +We welcome contributions to the Living Documentation Generator! Whether you're fixing bugs, improving documentation, or proposing new features, your help is appreciated. + +#### How to Contribute + +Before contributing, please review our [contribution guidelines](https://github.com/AbsaOSS/living-doc-utilities/blob/master/CONTRIBUTING.md) for more detailed information. + +### License Information + +This project is licensed under the Apache License 2.0. It is a liberal license that allows you great freedom in using, modifying, and distributing this software, while also providing an express grant of patent rights from contributors to users. + +For more details, see the [LICENSE](https://github.com/AbsaOSS/living-doc-utilities/blob/master/LICENSE) file in the repository. + +### Contact or Support Information + +If you need help with using or contributing to the Living Documentation Generator Action, or if you have any questions or feedback, don't hesitate to reach out: + +- **Issue Tracker**: For technical issues or feature requests, use the [GitHub Issues page](https://github.com/AbsaOSS/living-doc-utilities/issues). +- **Discussion Forum**: For general questions and discussions, join our [GitHub Discussions forum](https://github.com/AbsaOSS/living-doc-utilities/discussions). From 3a7eda9ec6eefbc2c84b2ac6425c0e82d0d4204c Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 10:11:43 +0200 Subject: [PATCH 04/33] Fixed all issues from pylint, black and mypy. --- src/living_doc_utilities/constants.py | 1 - src/living_doc_utilities/github/utils.py | 6 ++++++ src/living_doc_utilities/inputs/action_inputs.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/living_doc_utilities/constants.py b/src/living_doc_utilities/constants.py index 41aea89..3817a65 100644 --- a/src/living_doc_utilities/constants.py +++ b/src/living_doc_utilities/constants.py @@ -17,7 +17,6 @@ """ This module contains all constants and enums used across the project. """ -from enum import Enum # General Action inputs GITHUB_TOKEN = "GITHUB_TOKEN" diff --git a/src/living_doc_utilities/github/utils.py b/src/living_doc_utilities/github/utils.py index c4c5ed3..c0d80c1 100644 --- a/src/living_doc_utilities/github/utils.py +++ b/src/living_doc_utilities/github/utils.py @@ -13,7 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +""" +This module contains utility functions for GitHub Actions. +""" + import logging +import os logger = logging.getLogger(__name__) diff --git a/src/living_doc_utilities/inputs/action_inputs.py b/src/living_doc_utilities/inputs/action_inputs.py index b5ad04d..ec91cb7 100644 --- a/src/living_doc_utilities/inputs/action_inputs.py +++ b/src/living_doc_utilities/inputs/action_inputs.py @@ -19,7 +19,7 @@ which are essential for running the GH action. """ import logging -from abc import ABC +from abc import ABC, abstractmethod from src.living_doc_utilities.constants import GITHUB_TOKEN from src.living_doc_utilities.github.utils import get_action_input @@ -55,6 +55,7 @@ def validate_user_configuration(self) -> bool: logger.debug("User configuration validation successfully completed.") return True + @abstractmethod def _validate(self) -> int: ... def print_effective_configuration(self) -> None: @@ -65,4 +66,5 @@ def print_effective_configuration(self) -> None: logger.debug("GitHub token: %s", self.get_github_token()) self._print_effective_configuration() + @abstractmethod def _print_effective_configuration(self) -> None: ... From 67b3fd98072929ac1fc6f512e6b03b04e9e8de9d Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:29:37 +0200 Subject: [PATCH 05/33] Add unit tests. --- tests/living_doc_utilities/__init__.py | 0 tests/living_doc_utilities/conftest.py | 51 ++++++ tests/living_doc_utilities/github/__init__.py | 0 .../github/test_rate_limiter.py | 65 ++++++++ .../living_doc_utilities/github/test_utils.py | 64 ++++++++ tests/living_doc_utilities/inputs/__init__.py | 0 .../inputs/test_action_inputs.py | 63 +++++++ tests/living_doc_utilities/model/__init__.py | 0 .../living_doc_utilities/model/test_issue.py | 141 ++++++++++++++++ .../living_doc_utilities/model/test_issues.py | 155 ++++++++++++++++++ .../model/test_project_status.py | 106 ++++++++++++ tests/living_doc_utilities/test_decorators.py | 140 ++++++++++++++++ .../test_logging_config.py | 71 ++++++++ 13 files changed, 856 insertions(+) create mode 100644 tests/living_doc_utilities/__init__.py create mode 100644 tests/living_doc_utilities/conftest.py create mode 100644 tests/living_doc_utilities/github/__init__.py create mode 100644 tests/living_doc_utilities/github/test_rate_limiter.py create mode 100644 tests/living_doc_utilities/github/test_utils.py create mode 100644 tests/living_doc_utilities/inputs/__init__.py create mode 100644 tests/living_doc_utilities/inputs/test_action_inputs.py create mode 100644 tests/living_doc_utilities/model/__init__.py create mode 100644 tests/living_doc_utilities/model/test_issue.py create mode 100644 tests/living_doc_utilities/model/test_issues.py create mode 100644 tests/living_doc_utilities/model/test_project_status.py create mode 100644 tests/living_doc_utilities/test_decorators.py create mode 100644 tests/living_doc_utilities/test_logging_config.py diff --git a/tests/living_doc_utilities/__init__.py b/tests/living_doc_utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/living_doc_utilities/conftest.py b/tests/living_doc_utilities/conftest.py new file mode 100644 index 0000000..29bb728 --- /dev/null +++ b/tests/living_doc_utilities/conftest.py @@ -0,0 +1,51 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import time +import pytest + +from github import Github +from github.Rate import Rate +from github.RateLimit import RateLimit + +from src.living_doc_utilities.github.rate_limiter import GithubRateLimiter + + +@pytest.fixture +def rate_limiter(mocker, request): + mock_github_client = mocker.Mock(spec=Github) + mock_github_client.get_rate_limit.return_value = request.getfixturevalue("mock_rate_limiter") + return GithubRateLimiter(mock_github_client) + + +@pytest.fixture +def mock_rate_limiter(mocker): + mock_rate = mocker.Mock(spec=Rate) + mock_rate.timestamp = mocker.Mock(return_value=time.time() + 3600) + + mock_core = mocker.Mock(spec=RateLimit) + mock_core.reset = mock_rate + + mock = mocker.Mock(spec=GithubRateLimiter) + mock.core = mock_core + mock.core.remaining = 10 + + return mock + + +@pytest.fixture +def mock_logging_setup(mocker): + mock_log_config = mocker.patch("logging.basicConfig") + yield mock_log_config diff --git a/tests/living_doc_utilities/github/__init__.py b/tests/living_doc_utilities/github/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/living_doc_utilities/github/test_rate_limiter.py b/tests/living_doc_utilities/github/test_rate_limiter.py new file mode 100644 index 0000000..05af03e --- /dev/null +++ b/tests/living_doc_utilities/github/test_rate_limiter.py @@ -0,0 +1,65 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import time + + +# GithubRateLimiter __call__ method + + +def test_rate_limiter_extended_sleep_remaining_1(mocker, rate_limiter, mock_rate_limiter): + # Patch time.sleep to avoid actual delay and track call count + mock_sleep = mocker.patch("time.sleep", return_value=None) + mock_rate_limiter.core.remaining = 1 + + # Mock method to be wrapped + method_mock = mocker.Mock() + wrapped_method = rate_limiter(method_mock) + + wrapped_method() + + method_mock.assert_called_once() + mock_sleep.assert_called_once() + + +def test_rate_limiter_extended_sleep_remaining_10(mocker, rate_limiter): + # Patch time.sleep to avoid actual delay and track call count + mock_sleep = mocker.patch("time.sleep", return_value=None) + + # Mock method to be wrapped + method_mock = mocker.Mock() + wrapped_method = rate_limiter(method_mock) + + wrapped_method() + + method_mock.assert_called_once() + mock_sleep.assert_not_called() + + +def test_rate_limiter_extended_sleep_remaining_1_negative_reset_time(mocker, rate_limiter, mock_rate_limiter): + # Patch time.sleep to avoid actual delay and track call count + mock_sleep = mocker.patch("time.sleep", return_value=None) + mock_rate_limiter.core.remaining = 1 + mock_rate_limiter.core.reset.timestamp = mocker.Mock(return_value=time.time() - 1000) + + # Mock method to be wrapped + method_mock = mocker.Mock() + wrapped_method = rate_limiter(method_mock) + + wrapped_method() + + method_mock.assert_called_once() + mock_sleep.assert_called_once() diff --git a/tests/living_doc_utilities/github/test_utils.py b/tests/living_doc_utilities/github/test_utils.py new file mode 100644 index 0000000..2b52f04 --- /dev/null +++ b/tests/living_doc_utilities/github/test_utils.py @@ -0,0 +1,64 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from src.living_doc_utilities.github.utils import get_action_input, set_action_output + + +# GitHub action utils +# get_action_input + + +def test_get_input_with_hyphen(mocker): + mock_getenv = mocker.patch("os.getenv", return_value="test_value") + + actual = get_action_input("test-input") + + mock_getenv.assert_called_with("INPUT_TEST_INPUT", default='') + assert "test_value" == actual + + +def test_get_input_without_hyphen(mocker): + mock_getenv = mocker.patch("os.getenv", return_value="another_test_value") + + actual = get_action_input("anotherinput") + + mock_getenv.assert_called_with("INPUT_ANOTHERINPUT", default='') + assert "another_test_value" == actual + + +# set_action_output + + +def test_set_output_default(mocker): + mocker.patch("os.getenv", return_value="default_output.txt") + mock_open = mocker.patch("builtins.open", new_callable=mocker.mock_open) + + set_action_output("test-output", "test_value") + + mock_open.assert_called_with("default_output.txt", "a", encoding="utf-8") + handle = mock_open() + handle.write.assert_any_call("test-output=test_value\n") + + +def test_set_output_custom_path(mocker): + mocker.patch("os.getenv", return_value="custom_output.txt") + mock_open = mocker.patch("builtins.open", new_callable=mocker.mock_open) + + set_action_output("custom-output", "custom_value", "default_output.txt") + + mock_open.assert_called_with("custom_output.txt", "a", encoding="utf-8") + handle = mock_open() + handle.write.assert_any_call("custom-output=custom_value\n") diff --git a/tests/living_doc_utilities/inputs/__init__.py b/tests/living_doc_utilities/inputs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/living_doc_utilities/inputs/test_action_inputs.py b/tests/living_doc_utilities/inputs/test_action_inputs.py new file mode 100644 index 0000000..dc71b30 --- /dev/null +++ b/tests/living_doc_utilities/inputs/test_action_inputs.py @@ -0,0 +1,63 @@ +# +# Copyright 2024 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest + +from src.living_doc_utilities.inputs.action_inputs import BaseActionInputs + + +class TestActionInputs(BaseActionInputs): + def _validate(self) -> int: + return 0 # Mock implementation for testing + + def _print_effective_configuration(self) -> None: + pass # Mock implementation for testing + + +@pytest.fixture +def action_inputs(): + return TestActionInputs() + + +def test_get_github_token(mocker, action_inputs): + mock_get_action_input = mocker.patch( + "src.living_doc_utilities.inputs.action_inputs.get_action_input", + return_value="mock_token", + ) + token = action_inputs.get_github_token() + assert token == "mock_token" + mock_get_action_input.assert_called_once_with("GITHUB_TOKEN") + + +def test_validate_user_configuration_success(action_inputs): + assert action_inputs.validate_user_configuration() is True + + +def test_validate_user_configuration_failure(mocker, action_inputs): + mock_validate = mocker.patch.object(TestActionInputs, "_validate", return_value=1) + assert action_inputs.validate_user_configuration() is False + mock_validate.assert_called_once() + + +def test_print_effective_configuration(mocker, action_inputs): + mock_get_action_input = mocker.patch( + "src.living_doc_utilities.inputs.action_inputs.get_action_input", + return_value="mock_token", + ) + mock_print_config = mocker.patch.object(TestActionInputs, "_print_effective_configuration") + action_inputs.print_effective_configuration() + mock_get_action_input.assert_called_once_with("GITHUB_TOKEN") + mock_print_config.assert_called_once() diff --git a/tests/living_doc_utilities/model/__init__.py b/tests/living_doc_utilities/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/living_doc_utilities/model/test_issue.py b/tests/living_doc_utilities/model/test_issue.py new file mode 100644 index 0000000..b107a3e --- /dev/null +++ b/tests/living_doc_utilities/model/test_issue.py @@ -0,0 +1,141 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from src.living_doc_utilities.model.issue import Issue +from src.living_doc_utilities.model.project_status import ProjectStatus + + +def test_issue_initialization(): + # Arrange & Act + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + + # Assert + assert issue.repository_id == "org/repo" + assert issue.title == "Test Issue" + assert issue.issue_number == 1 + assert issue.state is None + assert issue.created_at is None + assert issue.updated_at is None + assert issue.closed_at is None + assert issue.html_url is None + assert issue.body is None + assert issue.labels is None + assert issue.linked_to_project is None + assert issue.project_statuses is None + + +def test_issue_to_dict(): + # Arrange + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + issue.state = "open" + issue.created_at = "2025-01-01T00:00:00Z" + issue.updated_at = "2025-02-01T00:00:00Z" + issue.closed_at = "2025-03-01T00:00:00Z" + issue.labels = ["bug", "enhancement"] + issue.linked_to_project = True + issue.html_url = "url" + issue.body = "body" + project_status = ProjectStatus() + project_status.project_title = "Test Project" + issue.project_statuses = [project_status] + + # Act + result = issue.to_dict() + + # Assert + assert result == { + "repository_id": "org/repo", + "title": "Test Issue", + "number": 1, + "html_url": "url", + "body": "body", + "state": "open", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-02-01T00:00:00Z", + "closed_at": "2025-03-01T00:00:00Z", + "labels": ["bug", "enhancement"], + "linked_to_project": True, + "project_status": [{"project_title": "Test Project", "status": "---", "priority": "---", "size": "---", "moscow": "---"}], + } + + +def test_issue_from_dict(): + # Arrange + data = { + "repository_id": "org/repo", + "title": "Test Issue", + "number": 1, + "state": "open", + "created_at": "2025-01-01T00:00:00Z", + "labels": ["bug", "enhancement"], + "linked_to_project": True, + "project_status": [{"project_title": "Test Project"}], + } + + # Act + issue = Issue.from_dict(data) + + # Assert + assert issue.repository_id == "org/repo" + assert issue.title == "Test Issue" + assert issue.issue_number == 1 + assert issue.state == "open" + assert issue.created_at == "2025-01-01T00:00:00Z" + assert issue.labels == ["bug", "enhancement"] + assert issue.linked_to_project is True + assert len(issue.project_statuses) == 1 + assert issue.project_statuses[0].project_title == "Test Project" + + +def test_issue_organization_name(): + # Arrange + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + + # Act + org_name = issue.organization_name() + + # Assert + assert org_name == "org" + + +def test_issue_repository_name(): + # Arrange + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + + # Act + repo_name = issue.repository_name() + + # Assert + assert repo_name == "repo" + + +def test_issue_from_dict_project_statuses_none(): + # Arrange + data = { + "repository_id": "org/repo", + "title": "Test Issue", + "number": 1, + "state": "open", + "created_at": "2025-01-01T00:00:00Z", + # No "project_status" key + } + + # Act + issue = Issue.from_dict(data) + + # Assert + assert issue.project_statuses is None diff --git a/tests/living_doc_utilities/model/test_issues.py b/tests/living_doc_utilities/model/test_issues.py new file mode 100644 index 0000000..d0aa86e --- /dev/null +++ b/tests/living_doc_utilities/model/test_issues.py @@ -0,0 +1,155 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from src.living_doc_utilities.model.issue import Issue +from src.living_doc_utilities.model.issues import Issues + + +def test_issues_initialization(): + # Arrange & Act + issues = Issues() + + # Assert + assert issues.issues == {} + + +def test_add_issue_with_key(): + # Arrange + issues = Issues() + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + key = "org/repo/1" + + # Act + issues.add_issue(key, issue) + + # Assert + assert key in issues.issues + assert issues.issues[key] == issue + + +def test_get_issue(): + # Arrange + issues = Issues() + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + key = "org/repo/1" + issues.add_issue(key, issue) + + # Act + retrieved_issue = issues.get_issue(key) + + # Assert + assert retrieved_issue == issue + + +def test_all_issues(): + # Arrange + issues = Issues() + issue1 = Issue(repository_id="org/repo1", title="Issue 1", number=1) + issue2 = Issue(repository_id="org/repo2", title="Issue 2", number=2) + issues.add_issue("org/repo1/1", issue1) + issues.add_issue("org/repo2/2", issue2) + + # Act + all_issues = issues.all_issues() + + # Assert + assert len(all_issues) == 2 + assert "org/repo1/1" in all_issues + assert "org/repo2/2" in all_issues + assert all_issues["org/repo1/1"] == issue1 + assert all_issues["org/repo2/2"] == issue2 + + +def test_count(): + # Arrange + issues = Issues() + issue1 = Issue(repository_id="org/repo1", title="Issue 1", number=1) + issue2 = Issue(repository_id="org/repo2", title="Issue 2", number=2) + issues.add_issue("org/repo1/1", issue1) + issues.add_issue("org/repo2/2", issue2) + + # Act + count = issues.count() + + # Assert + assert count == 2 + + +def test_make_issue_key(): + # Arrange + organization_name = "org" + repository_name = "repo" + issue_number = 1 + + # Act + key = Issues.make_issue_key(organization_name, repository_name, issue_number) + + # Assert + assert key == "org/repo/1" + + +def test_save_to_json(tmp_path): + # Arrange + issues = Issues() + issue = Issue(repository_id="org/repo", title="Test Issue", number=1) + key = "org/repo/1" + issues.add_issue(key, issue) + file_path = tmp_path / "issues.json" + + # Act + issues.save_to_json(file_path) + + # Assert + with open(file_path, "r", encoding="utf-8") as f: + data = f.read() + assert '"org/repo/1"' in data + assert '"Test Issue"' in data + + +def test_load_from_json(tmp_path): + # Arrange + file_path = tmp_path / "issues.json" + data = { + "org/repo/1": { + "repository_id": "org/repo", + "title": "Test Issue", + "number": 1, + "state": "open", + "created_at": "2025-01-01T00:00:00Z", + "labels": ["bug"], + "linked_to_project": True, + "project_status": [{"project_title": "Test Project"}], + } + } + with open(file_path, "w", encoding="utf-8") as f: + import json + json.dump(data, f) + + # Act + issues = Issues.load_from_json(file_path) + + # Assert + assert len(issues.issues) == 1 + issue = issues.issues["org/repo/1"] + assert issue.repository_id == "org/repo" + assert issue.title == "Test Issue" + assert issue.issue_number == 1 + assert issue.state == "open" + assert issue.created_at == "2025-01-01T00:00:00Z" + assert issue.labels == ["bug"] + assert issue.linked_to_project is True + assert len(issue.project_statuses) == 1 + assert issue.project_statuses[0].project_title == "Test Project" diff --git a/tests/living_doc_utilities/model/test_project_status.py b/tests/living_doc_utilities/model/test_project_status.py new file mode 100644 index 0000000..f1f74b7 --- /dev/null +++ b/tests/living_doc_utilities/model/test_project_status.py @@ -0,0 +1,106 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from src.living_doc_utilities.constants import NO_PROJECT_DATA +from src.living_doc_utilities.model.project_status import ProjectStatus + + +def test_project_status_initialization(): + # Arrange & Act + project_status = ProjectStatus() + + # Assert + assert project_status.project_title == NO_PROJECT_DATA + assert project_status.status == NO_PROJECT_DATA + assert project_status.priority == NO_PROJECT_DATA + assert project_status.size == NO_PROJECT_DATA + assert project_status.moscow == NO_PROJECT_DATA + + +def test_project_status_setters_and_getters(): + # Arrange + project_status = ProjectStatus() + + # Act + project_status.project_title = "Test Project" + project_status.status = "In Progress" + project_status.priority = "High" + project_status.size = "Large" + project_status.moscow = "Must Have" + + # Assert + assert project_status.project_title == "Test Project" + assert project_status.status == "In Progress" + assert project_status.priority == "High" + assert project_status.size == "Large" + assert project_status.moscow == "Must Have" + + +def test_project_status_to_dict(): + # Arrange + project_status = ProjectStatus() + project_status.project_title = "Test Project" + project_status.status = "In Progress" + project_status.priority = "High" + project_status.size = "Large" + project_status.moscow = "Must Have" + + # Act + result = project_status.to_dict() + + # Assert + assert result == { + "project_title": "Test Project", + "status": "In Progress", + "priority": "High", + "size": "Large", + "moscow": "Must Have", + } + + +def test_project_status_from_dict(): + # Arrange + data = { + "project_title": "Test Project", + "status": "In Progress", + "priority": "High", + "size": "Large", + "moscow": "Must Have", + } + + # Act + project_status = ProjectStatus.from_dict(data) + + # Assert + assert project_status.project_title == "Test Project" + assert project_status.status == "In Progress" + assert project_status.priority == "High" + assert project_status.size == "Large" + assert project_status.moscow == "Must Have" + + +def test_project_status_from_dict_with_missing_fields(): + # Arrange + data = {"project_title": "Test Project"} + + # Act + project_status = ProjectStatus.from_dict(data) + + # Assert + assert project_status.project_title == "Test Project" + assert project_status.status == NO_PROJECT_DATA + assert project_status.priority == NO_PROJECT_DATA + assert project_status.size == NO_PROJECT_DATA + assert project_status.moscow == NO_PROJECT_DATA diff --git a/tests/living_doc_utilities/test_decorators.py b/tests/living_doc_utilities/test_decorators.py new file mode 100644 index 0000000..f7b9fc3 --- /dev/null +++ b/tests/living_doc_utilities/test_decorators.py @@ -0,0 +1,140 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from github import GithubException +from requests import RequestException + +from src.living_doc_utilities.decorators import debug_log_decorator, safe_call_decorator + + +# sample function to be decorated +def sample_function(x, y): + return x + y + + +# debug_log_decorator + + +def test_debug_log_decorator(mocker): + # Mock logging + mock_log_debug = mocker.patch("src.living_doc_utilities.decorators.logger.debug") + + decorated_function = debug_log_decorator(sample_function) + expected_call = [ + mocker.call("Calling method %s with args: %s and kwargs: %s.", "sample_function", (3, 4), {}), + mocker.call("Method %s returned %s.", "sample_function", 7), + ] + + actual = decorated_function(3, 4) + + assert 7 == actual + assert mock_log_debug.call_args_list == expected_call + + +# safe_call_decorator + + +def test_safe_call_decorator_success(rate_limiter): + @safe_call_decorator(rate_limiter) + def sample_method(x, y): + return x + y + + actual = sample_method(2, 3) + + assert 5 == actual + + +def test_safe_call_decorator_network_error(rate_limiter, mocker): + mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + + @safe_call_decorator(rate_limiter) + def sample_method(): + raise ConnectionError("Test connection error") + + actual = sample_method() + + args, kwargs = mock_log_error.call_args + assert actual is None + assert 1 == mock_log_error.call_count + assert "Network error calling %s: %s." == args[0] + assert "sample_method" == args[1] + assert isinstance(args[2], ConnectionError) + assert "Test connection error" == str(args[2]) + assert kwargs["exc_info"] + + +def test_safe_call_decorator_github_api_error(rate_limiter, mocker): + mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + + @safe_call_decorator(rate_limiter) + def sample_method(): + status_code = 404 + error_data = {"message": "Not Found", "documentation_url": "https://developer.github.com/v3"} + response_headers = { + "X-RateLimit-Limit": "60", + "X-RateLimit-Remaining": "0", + } + raise GithubException(status_code, error_data, response_headers) + + actual = sample_method() + + args, kwargs = mock_log_error.call_args + assert actual is None + assert 1 == mock_log_error.call_count + assert "GitHub API error calling %s: %s." == args[0] + assert "sample_method" == args[1] + assert isinstance(args[2], GithubException) + assert '404 {"message": "Not Found", "documentation_url": "https://developer.github.com/v3"}' == str(args[2]) + assert kwargs["exc_info"] + + +def test_safe_call_decorator_http_error(mocker, rate_limiter): + mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + + @safe_call_decorator(rate_limiter) + def sample_method(): + raise RequestException("Test HTTP error") + + actual = sample_method() + + args, kwargs = mock_log_error.call_args + assert actual is None + assert 1 == mock_log_error.call_count + assert "HTTP error calling %s: %s." == args[0] + assert "sample_method" == args[1] + assert isinstance(args[2], RequestException) + assert "Test HTTP error" == str(args[2]) + assert kwargs["exc_info"] + + +def test_safe_call_decorator_exception(rate_limiter, mocker): + mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + + @safe_call_decorator(rate_limiter) + def sample_method(x, y): + return x / y + + actual = sample_method(2, 0) + + mock_log_error.assert_called_once() + exception_message = mock_log_error.call_args[0][0] + exception_type = mock_log_error.call_args[0][1] + method_name = mock_log_error.call_args[0][2] + + assert actual is None + assert "%s by calling %s: %s." in exception_message + assert "ZeroDivisionError" in exception_type + assert "sample_method" in method_name diff --git a/tests/living_doc_utilities/test_logging_config.py b/tests/living_doc_utilities/test_logging_config.py new file mode 100644 index 0000000..32e980e --- /dev/null +++ b/tests/living_doc_utilities/test_logging_config.py @@ -0,0 +1,71 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging +import os +import sys +from logging import StreamHandler + +from src.living_doc_utilities.logging_config import setup_logging + + +def validate_logging_config(mock_logging_setup, caplog, expected_level, expected_message): + mock_logging_setup.assert_called_once() + + # Get the actual call arguments from the mock + call_args = mock_logging_setup.call_args[1] # Extract the kwargs from the call + + # Validate the logging level and format + assert call_args["level"] == expected_level + assert call_args["format"] == "%(asctime)s - %(levelname)s - %(message)s" + assert call_args["datefmt"] == "%Y-%m-%d %H:%M:%S" + + # Check that the handler is a StreamHandler and outputs to sys.stdout + handlers = call_args["handlers"] + assert 1 == len(handlers) # Only one handler is expected + assert isinstance(handlers[0], StreamHandler) # Handler should be StreamHandler + assert handlers[0].stream is sys.stdout # Stream should be sys.stdout + + # Check that the log message is present + assert expected_message in caplog.text + + +# setup_logging + + +def test_setup_logging_default_logging_level(mock_logging_setup, caplog): + with caplog.at_level(logging.INFO): + setup_logging() + + validate_logging_config(mock_logging_setup, caplog, logging.INFO, "Logging configuration set up.") + + +def test_setup_logging_verbose_logging_enabled(mock_logging_setup, caplog): + os.environ["INPUT_VERBOSE_LOGGING"] = "true" + + with caplog.at_level(logging.DEBUG): + setup_logging() + + validate_logging_config(mock_logging_setup, caplog, logging.DEBUG, "Verbose logging enabled.") + + +def test_setup_logging_debug_mode_enabled_by_ci(mock_logging_setup, caplog): + os.environ["RUNNER_DEBUG"] = "1" + + with caplog.at_level(logging.DEBUG): + setup_logging() + + validate_logging_config(mock_logging_setup, caplog, logging.DEBUG, "Debug mode enabled by CI runner.") From a81d887d210dd42394ee8c7d6f7636edccff3f17 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:32:49 +0200 Subject: [PATCH 06/33] Add a workflow with basic checks. --- .../workflows/static_analysis_and_tests.yml | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .github/workflows/static_analysis_and_tests.yml diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml new file mode 100644 index 0000000..6ea3331 --- /dev/null +++ b/.github/workflows/static_analysis_and_tests.yml @@ -0,0 +1,128 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: "Static analysis & tests" + +on: + push: + branches: [ "master" ] + pull_request: + +jobs: + analysis: + runs-on: ubuntu-latest + name: Pylint Analysis + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.5 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Analyze code with Pylint + id: analyze-code + run: | + pylint_score=$(pylint $(git ls-files '*.py') | grep 'rated at' | awk '{print $7}' | cut -d'/' -f1) + echo "PYLINT_SCORE=$pylint_score" >> $GITHUB_ENV + + - name: Check Pylint score + run: | + if (( $(echo "$PYLINT_SCORE < 9.5" | bc -l) )); then + echo "Failure: Pylint score is below 9.5 (project score: $PYLINT_SCORE)." + exit 1 + else + echo "Success: Pylint score is above 9.5 (project score: $PYLINT_SCORE)." + fi + + code-format-check: + runs-on: ubuntu-latest + name: Black Format Check + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.5 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Check code format with Black + id: check-format + run: | + black --check $(git ls-files '*.py') + + mypy-check: + runs-on: ubuntu-latest + name: Mypy Type Check + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.5 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Check types with Mypy + id: check-types + run: | + mypy . + + python-tests: + runs-on: ubuntu-latest + name: Python Tests + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Check code coverage with Pytest + run: pytest --ignore=tests/integration --cov=. -v tests/ --cov-fail-under=80 From 2193d45dfc7aac4dbea028647f6f6b4260839373 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:35:53 +0200 Subject: [PATCH 07/33] Fix workflows. --- .../workflows/static_analysis_and_tests.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 6ea3331..fae1dae 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -41,6 +41,12 @@ jobs: run: | pip install -r requirements.txt + - name: Set PROJECT_ROOT and update PYTHONPATH + run: | + ACTION_ROOT="${{ github.action_path }}" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + shell: bash + - name: Analyze code with Pylint id: analyze-code run: | @@ -75,6 +81,12 @@ jobs: run: | pip install -r requirements.txt + - name: Set PROJECT_ROOT and update PYTHONPATH + run: | + ACTION_ROOT="${{ github.action_path }}" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + shell: bash + - name: Check code format with Black id: check-format run: | @@ -99,6 +111,12 @@ jobs: run: | pip install -r requirements.txt + - name: Set PROJECT_ROOT and update PYTHONPATH + run: | + ACTION_ROOT="${{ github.action_path }}" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + shell: bash + - name: Check types with Mypy id: check-types run: | @@ -124,5 +142,11 @@ jobs: run: | pip install -r requirements.txt + - name: Set PROJECT_ROOT and update PYTHONPATH + run: | + ACTION_ROOT="${{ github.action_path }}" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + shell: bash + - name: Check code coverage with Pytest run: pytest --ignore=tests/integration --cov=. -v tests/ --cov-fail-under=80 From 4b0f1336f5c422136bb6005b8c550418316b6202 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:39:21 +0200 Subject: [PATCH 08/33] Fix workflows. --- .github/workflows/static_analysis_and_tests.yml | 8 ++++---- DEVELOPER.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index fae1dae..a6510aa 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -44,7 +44,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.action_path }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash - name: Analyze code with Pylint @@ -84,7 +84,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.action_path }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash - name: Check code format with Black @@ -114,7 +114,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.action_path }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash - name: Check types with Mypy @@ -145,7 +145,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.action_path }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-generator" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash - name: Check code coverage with Pytest diff --git a/DEVELOPER.md b/DEVELOPER.md index fa30500..3291811 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -145,7 +145,7 @@ Success: no issues found in 1 source file Unit tests are written using the Pytest framework. To run all the tests, use the following command: ```shell -pytest --ignore=tests/integration tests/ +pytest tests/ ``` You can modify the directory to control the level of detail or granularity as per your needs. @@ -163,7 +163,7 @@ The objective of the project is to achieve a minimum score of 80 %. We do exclud To generate the coverage report, run the following command: ```shell -pytest --ignore=tests/integration --cov=. tests/ --cov-fail-under=80 --cov-report=html +pytest --cov=. tests/ --cov-fail-under=80 --cov-report=html ``` See the coverage report on the path: From eb534b0fbae53a9306117d763a730c49bf45863e Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:49:52 +0200 Subject: [PATCH 09/33] Fix workflows. --- .github/workflows/static_analysis_and_tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index a6510aa..7a6e2a2 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -43,7 +43,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | - ACTION_ROOT="${{ github.action_path }}" + ACTION_ROOT="${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash @@ -83,7 +83,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | - ACTION_ROOT="${{ github.action_path }}" + ACTION_ROOT="${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash @@ -113,7 +113,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | - ACTION_ROOT="${{ github.action_path }}" + ACTION_ROOT="${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash @@ -144,9 +144,9 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | - ACTION_ROOT="${{ github.action_path }}" + ACTION_ROOT="${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" shell: bash - name: Check code coverage with Pytest - run: pytest --ignore=tests/integration --cov=. -v tests/ --cov-fail-under=80 + run: pytest --cov=. -v tests/ --cov-fail-under=80 From f8b48282b9013ca51fb207284d4059a7f70b45c5 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:53:57 +0200 Subject: [PATCH 10/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 7a6e2a2..160c8db 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -34,7 +34,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5.1.0 with: - python-version: '3.11' + python-version: '3.12' cache: 'pip' - name: Install dependencies @@ -74,7 +74,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5.1.0 with: - python-version: '3.11' + python-version: '3.12' cache: 'pip' - name: Install dependencies @@ -104,7 +104,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5.1.0 with: - python-version: '3.11' + python-version: '3.12' cache: 'pip' - name: Install dependencies @@ -114,7 +114,9 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" + echo "${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + echo "${PYTHONPATH}" shell: bash - name: Check types with Mypy @@ -135,7 +137,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5.1.0 with: - python-version: '3.11' + python-version: '3.12' cache: 'pip' - name: Install dependencies From 5e8f1f88c382352de2132176584d65888e7fd34e Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:55:52 +0200 Subject: [PATCH 11/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 160c8db..9994d64 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -110,13 +110,12 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt + echo "${{ github.workspace }}" - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - echo "${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" - echo "${PYTHONPATH}" shell: bash - name: Check types with Mypy From 4066ace0fa5902aafad264ca1367c7dcbb3b671c Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 13:57:42 +0200 Subject: [PATCH 12/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 9994d64..39c692e 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -110,12 +110,11 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt - echo "${{ github.workspace }}" - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" shell: bash - name: Check types with Mypy From dfbc6d9b0ac7d5f17f26f5df397d6a55461360fa Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:06:27 +0200 Subject: [PATCH 13/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 39c692e..3f7642e 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -44,7 +44,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" shell: bash - name: Analyze code with Pylint @@ -84,7 +84,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" shell: bash - name: Check code format with Black @@ -145,7 +145,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" shell: bash - name: Check code coverage with Pytest From 7eb9599ea8a710db3c3e8c4f108b8bd56111b9e9 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:09:16 +0200 Subject: [PATCH 14/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 3f7642e..494ac1e 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -141,11 +141,13 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt + echo "${{ github.workspace }}" - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" + echo "${PYTHONPATH}" shell: bash - name: Check code coverage with Pytest From efcad1f9af487dde982622b1810602feee34e584 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:14:34 +0200 Subject: [PATCH 15/33] Removed src dir. --- DEVELOPER.md | 2 ++ .../__init__.py | 0 .../constants.py | 0 .../decorators.py | 2 +- .../github/__init__.py | 0 .../github/rate_limiter.py | 0 .../github/utils.py | 0 .../inputs/__init__.py | 0 .../inputs/action_inputs.py | 4 ++-- .../logging_config.py | 0 .../model/__init__.py | 0 .../model/issue.py | 2 +- .../model/issues.py | 2 +- .../model/project_status.py | 2 +- src/__init__.py | 15 --------------- tests/living_doc_utilities/conftest.py | 2 +- tests/living_doc_utilities/github/test_utils.py | 2 +- .../inputs/test_action_inputs.py | 2 +- tests/living_doc_utilities/model/test_issue.py | 4 ++-- tests/living_doc_utilities/model/test_issues.py | 4 ++-- .../model/test_project_status.py | 4 ++-- tests/living_doc_utilities/test_decorators.py | 2 +- tests/living_doc_utilities/test_logging_config.py | 2 +- 23 files changed, 19 insertions(+), 32 deletions(-) rename {src/living_doc_utilities => living_doc_utilities}/__init__.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/constants.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/decorators.py (97%) rename {src/living_doc_utilities => living_doc_utilities}/github/__init__.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/github/rate_limiter.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/github/utils.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/inputs/__init__.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/inputs/action_inputs.py (94%) rename {src/living_doc_utilities => living_doc_utilities}/logging_config.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/model/__init__.py (100%) rename {src/living_doc_utilities => living_doc_utilities}/model/issue.py (98%) rename {src/living_doc_utilities => living_doc_utilities}/model/issues.py (97%) rename {src/living_doc_utilities => living_doc_utilities}/model/project_status.py (97%) delete mode 100644 src/__init__.py diff --git a/DEVELOPER.md b/DEVELOPER.md index 3291811..39e9700 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -186,6 +186,8 @@ open htmlcov/index.html Ensure your pyproject.toml includes this structure: +TODO - update to latest version + ```toml [build-system] requires = ["setuptools>=61.0"] diff --git a/src/living_doc_utilities/__init__.py b/living_doc_utilities/__init__.py similarity index 100% rename from src/living_doc_utilities/__init__.py rename to living_doc_utilities/__init__.py diff --git a/src/living_doc_utilities/constants.py b/living_doc_utilities/constants.py similarity index 100% rename from src/living_doc_utilities/constants.py rename to living_doc_utilities/constants.py diff --git a/src/living_doc_utilities/decorators.py b/living_doc_utilities/decorators.py similarity index 97% rename from src/living_doc_utilities/decorators.py rename to living_doc_utilities/decorators.py index bde0a13..f071083 100644 --- a/src/living_doc_utilities/decorators.py +++ b/living_doc_utilities/decorators.py @@ -26,7 +26,7 @@ from github import GithubException from requests import Timeout, RequestException -from src.living_doc_utilities.github.rate_limiter import GithubRateLimiter +from living_doc_utilities.github.rate_limiter import GithubRateLimiter logger = logging.getLogger(__name__) diff --git a/src/living_doc_utilities/github/__init__.py b/living_doc_utilities/github/__init__.py similarity index 100% rename from src/living_doc_utilities/github/__init__.py rename to living_doc_utilities/github/__init__.py diff --git a/src/living_doc_utilities/github/rate_limiter.py b/living_doc_utilities/github/rate_limiter.py similarity index 100% rename from src/living_doc_utilities/github/rate_limiter.py rename to living_doc_utilities/github/rate_limiter.py diff --git a/src/living_doc_utilities/github/utils.py b/living_doc_utilities/github/utils.py similarity index 100% rename from src/living_doc_utilities/github/utils.py rename to living_doc_utilities/github/utils.py diff --git a/src/living_doc_utilities/inputs/__init__.py b/living_doc_utilities/inputs/__init__.py similarity index 100% rename from src/living_doc_utilities/inputs/__init__.py rename to living_doc_utilities/inputs/__init__.py diff --git a/src/living_doc_utilities/inputs/action_inputs.py b/living_doc_utilities/inputs/action_inputs.py similarity index 94% rename from src/living_doc_utilities/inputs/action_inputs.py rename to living_doc_utilities/inputs/action_inputs.py index ec91cb7..fd1b56e 100644 --- a/src/living_doc_utilities/inputs/action_inputs.py +++ b/living_doc_utilities/inputs/action_inputs.py @@ -21,8 +21,8 @@ import logging from abc import ABC, abstractmethod -from src.living_doc_utilities.constants import GITHUB_TOKEN -from src.living_doc_utilities.github.utils import get_action_input +from living_doc_utilities.constants import GITHUB_TOKEN +from living_doc_utilities.github.utils import get_action_input logger = logging.getLogger(__name__) diff --git a/src/living_doc_utilities/logging_config.py b/living_doc_utilities/logging_config.py similarity index 100% rename from src/living_doc_utilities/logging_config.py rename to living_doc_utilities/logging_config.py diff --git a/src/living_doc_utilities/model/__init__.py b/living_doc_utilities/model/__init__.py similarity index 100% rename from src/living_doc_utilities/model/__init__.py rename to living_doc_utilities/model/__init__.py diff --git a/src/living_doc_utilities/model/issue.py b/living_doc_utilities/model/issue.py similarity index 98% rename from src/living_doc_utilities/model/issue.py rename to living_doc_utilities/model/issue.py index bb00b39..7a294ab 100644 --- a/src/living_doc_utilities/model/issue.py +++ b/living_doc_utilities/model/issue.py @@ -20,7 +20,7 @@ from typing import Any, Optional -from src.living_doc_utilities.model.project_status import ProjectStatus +from living_doc_utilities.model.project_status import ProjectStatus # pylint: disable=too-many-instance-attributes diff --git a/src/living_doc_utilities/model/issues.py b/living_doc_utilities/model/issues.py similarity index 97% rename from src/living_doc_utilities/model/issues.py rename to living_doc_utilities/model/issues.py index 6326f03..c52712f 100644 --- a/src/living_doc_utilities/model/issues.py +++ b/living_doc_utilities/model/issues.py @@ -22,7 +22,7 @@ from pathlib import Path from typing import Optional -from src.living_doc_utilities.model.issue import Issue +from living_doc_utilities.model.issue import Issue class Issues: diff --git a/src/living_doc_utilities/model/project_status.py b/living_doc_utilities/model/project_status.py similarity index 97% rename from src/living_doc_utilities/model/project_status.py rename to living_doc_utilities/model/project_status.py index 524c8c8..17a50e4 100644 --- a/src/living_doc_utilities/model/project_status.py +++ b/living_doc_utilities/model/project_status.py @@ -17,7 +17,7 @@ """ This module contains a data container for an issue's Project Status. """ -from src.living_doc_utilities.constants import NO_PROJECT_DATA +from living_doc_utilities.constants import NO_PROJECT_DATA # pylint: disable=too-many-instance-attributes diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index f7115cb..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright 2025 ABSA Group Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/tests/living_doc_utilities/conftest.py b/tests/living_doc_utilities/conftest.py index 29bb728..f228af0 100644 --- a/tests/living_doc_utilities/conftest.py +++ b/tests/living_doc_utilities/conftest.py @@ -20,7 +20,7 @@ from github.Rate import Rate from github.RateLimit import RateLimit -from src.living_doc_utilities.github.rate_limiter import GithubRateLimiter +from living_doc_utilities.github.rate_limiter import GithubRateLimiter @pytest.fixture diff --git a/tests/living_doc_utilities/github/test_utils.py b/tests/living_doc_utilities/github/test_utils.py index 2b52f04..033adb3 100644 --- a/tests/living_doc_utilities/github/test_utils.py +++ b/tests/living_doc_utilities/github/test_utils.py @@ -14,7 +14,7 @@ # limitations under the License. # -from src.living_doc_utilities.github.utils import get_action_input, set_action_output +from living_doc_utilities.github.utils import get_action_input, set_action_output # GitHub action utils diff --git a/tests/living_doc_utilities/inputs/test_action_inputs.py b/tests/living_doc_utilities/inputs/test_action_inputs.py index dc71b30..2b1f93b 100644 --- a/tests/living_doc_utilities/inputs/test_action_inputs.py +++ b/tests/living_doc_utilities/inputs/test_action_inputs.py @@ -16,7 +16,7 @@ import pytest -from src.living_doc_utilities.inputs.action_inputs import BaseActionInputs +from living_doc_utilities import BaseActionInputs class TestActionInputs(BaseActionInputs): diff --git a/tests/living_doc_utilities/model/test_issue.py b/tests/living_doc_utilities/model/test_issue.py index b107a3e..0860d25 100644 --- a/tests/living_doc_utilities/model/test_issue.py +++ b/tests/living_doc_utilities/model/test_issue.py @@ -15,8 +15,8 @@ # -from src.living_doc_utilities.model.issue import Issue -from src.living_doc_utilities.model.project_status import ProjectStatus +from living_doc_utilities.model.issue import Issue +from living_doc_utilities.model.project_status import ProjectStatus def test_issue_initialization(): diff --git a/tests/living_doc_utilities/model/test_issues.py b/tests/living_doc_utilities/model/test_issues.py index d0aa86e..d95258a 100644 --- a/tests/living_doc_utilities/model/test_issues.py +++ b/tests/living_doc_utilities/model/test_issues.py @@ -14,8 +14,8 @@ # limitations under the License. # -from src.living_doc_utilities.model.issue import Issue -from src.living_doc_utilities.model.issues import Issues +from living_doc_utilities.model.issue import Issue +from living_doc_utilities.model.issues import Issues def test_issues_initialization(): diff --git a/tests/living_doc_utilities/model/test_project_status.py b/tests/living_doc_utilities/model/test_project_status.py index f1f74b7..99ed249 100644 --- a/tests/living_doc_utilities/model/test_project_status.py +++ b/tests/living_doc_utilities/model/test_project_status.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from src.living_doc_utilities.constants import NO_PROJECT_DATA -from src.living_doc_utilities.model.project_status import ProjectStatus +from living_doc_utilities import NO_PROJECT_DATA +from living_doc_utilities.model.project_status import ProjectStatus def test_project_status_initialization(): diff --git a/tests/living_doc_utilities/test_decorators.py b/tests/living_doc_utilities/test_decorators.py index f7b9fc3..4b7bb89 100644 --- a/tests/living_doc_utilities/test_decorators.py +++ b/tests/living_doc_utilities/test_decorators.py @@ -17,7 +17,7 @@ from github import GithubException from requests import RequestException -from src.living_doc_utilities.decorators import debug_log_decorator, safe_call_decorator +from living_doc_utilities import debug_log_decorator, safe_call_decorator # sample function to be decorated diff --git a/tests/living_doc_utilities/test_logging_config.py b/tests/living_doc_utilities/test_logging_config.py index 32e980e..20adfac 100644 --- a/tests/living_doc_utilities/test_logging_config.py +++ b/tests/living_doc_utilities/test_logging_config.py @@ -19,7 +19,7 @@ import sys from logging import StreamHandler -from src.living_doc_utilities.logging_config import setup_logging +from living_doc_utilities.logging_config import setup_logging def validate_logging_config(mock_logging_setup, caplog, expected_level, expected_message): From 10de8fd1fa88360f6ea805c2b63b6c6936151f99 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:17:17 +0200 Subject: [PATCH 16/33] Testing. --- .github/workflows/check_pr_release_notes.yml | 2 +- .../workflows/static_analysis_and_tests.yml | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml index 3f2d6d6..f233e06 100644 --- a/.github/workflows/check_pr_release_notes.yml +++ b/.github/workflows/check_pr_release_notes.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/setup-python@v5.1.1 with: - python-version: '3.11' + python-version: '3.12' - name: Check presence of release notes in PR description uses: AbsaOSS/release-notes-presence-check@v0.3.0 diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 494ac1e..33486f8 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -41,11 +41,11 @@ jobs: run: | pip install -r requirements.txt - - name: Set PROJECT_ROOT and update PYTHONPATH - run: | - ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" - shell: bash +# - name: Set PROJECT_ROOT and update PYTHONPATH +# run: | +# ACTION_ROOT="${{ github.workspace }}" +# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" +# shell: bash - name: Analyze code with Pylint id: analyze-code @@ -81,11 +81,11 @@ jobs: run: | pip install -r requirements.txt - - name: Set PROJECT_ROOT and update PYTHONPATH - run: | - ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" - shell: bash +# - name: Set PROJECT_ROOT and update PYTHONPATH +# run: | +# ACTION_ROOT="${{ github.workspace }}" +# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" +# shell: bash - name: Check code format with Black id: check-format @@ -111,11 +111,11 @@ jobs: run: | pip install -r requirements.txt - - name: Set PROJECT_ROOT and update PYTHONPATH - run: | - ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" - shell: bash +# - name: Set PROJECT_ROOT and update PYTHONPATH +# run: | +# ACTION_ROOT="${{ github.workspace }}" +# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" +# shell: bash - name: Check types with Mypy id: check-types @@ -143,12 +143,12 @@ jobs: pip install -r requirements.txt echo "${{ github.workspace }}" - - name: Set PROJECT_ROOT and update PYTHONPATH - run: | - ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" - echo "${PYTHONPATH}" - shell: bash +# - name: Set PROJECT_ROOT and update PYTHONPATH +# run: | +# ACTION_ROOT="${{ github.workspace }}" +# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" +# echo "${PYTHONPATH}" +# shell: bash - name: Check code coverage with Pytest run: pytest --cov=. -v tests/ --cov-fail-under=80 From 50800c0f21a4e79e588506ac2b6795be7fd9fa6b Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:18:35 +0200 Subject: [PATCH 17/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 33486f8..448700d 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -143,12 +143,12 @@ jobs: pip install -r requirements.txt echo "${{ github.workspace }}" -# - name: Set PROJECT_ROOT and update PYTHONPATH -# run: | -# ACTION_ROOT="${{ github.workspace }}" -# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" -# echo "${PYTHONPATH}" -# shell: bash + - name: Set PROJECT_ROOT and update PYTHONPATH + run: | + ACTION_ROOT="${{ github.workspace }}" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + echo "${PYTHONPATH}" + shell: bash - name: Check code coverage with Pytest run: pytest --cov=. -v tests/ --cov-fail-under=80 From 895deee16743e2d5f39ff3d8f484c4be7c61c4c6 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:20:32 +0200 Subject: [PATCH 18/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 448700d..1257ef2 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -146,7 +146,7 @@ jobs: - name: Set PROJECT_ROOT and update PYTHONPATH run: | ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" + export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/.." echo "${PYTHONPATH}" shell: bash From a9ae7ef088634483e53d0728097871936936957b Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:26:25 +0200 Subject: [PATCH 19/33] Testing. --- .../workflows/static_analysis_and_tests.yml | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 1257ef2..5a32cf7 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -41,12 +41,6 @@ jobs: run: | pip install -r requirements.txt -# - name: Set PROJECT_ROOT and update PYTHONPATH -# run: | -# ACTION_ROOT="${{ github.workspace }}" -# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/living-doc-utilities" -# shell: bash - - name: Analyze code with Pylint id: analyze-code run: | @@ -81,12 +75,6 @@ jobs: run: | pip install -r requirements.txt -# - name: Set PROJECT_ROOT and update PYTHONPATH -# run: | -# ACTION_ROOT="${{ github.workspace }}" -# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" -# shell: bash - - name: Check code format with Black id: check-format run: | @@ -111,12 +99,6 @@ jobs: run: | pip install -r requirements.txt -# - name: Set PROJECT_ROOT and update PYTHONPATH -# run: | -# ACTION_ROOT="${{ github.workspace }}" -# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}" -# shell: bash - - name: Check types with Mypy id: check-types run: | @@ -143,12 +125,13 @@ jobs: pip install -r requirements.txt echo "${{ github.workspace }}" - - name: Set PROJECT_ROOT and update PYTHONPATH - run: | - ACTION_ROOT="${{ github.workspace }}" - export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/.." - echo "${PYTHONPATH}" - shell: bash +# - name: Set PROJECT_ROOT and update PYTHONPATH +# run: | +# ACTION_ROOT="${{ github.workspace }}" +# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/.." +# echo "${PYTHONPATH}" +# shell: bash - name: Check code coverage with Pytest + working-directory: "${{ github.workspace }}" run: pytest --cov=. -v tests/ --cov-fail-under=80 From f0b003954aa25bc255ec90154302b678abfe0feb Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:32:04 +0200 Subject: [PATCH 20/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 12 +++--------- living_doc_utilities/__init__.py | 15 +++++++++++++++ living_doc_utilities/github/__init__.py | 15 +++++++++++++++ living_doc_utilities/inputs/__init__.py | 15 +++++++++++++++ living_doc_utilities/model/__init__.py | 15 +++++++++++++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 5a32cf7..7040f56 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -125,13 +125,7 @@ jobs: pip install -r requirements.txt echo "${{ github.workspace }}" -# - name: Set PROJECT_ROOT and update PYTHONPATH -# run: | -# ACTION_ROOT="${{ github.workspace }}" -# export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/.." -# echo "${PYTHONPATH}" -# shell: bash - - name: Check code coverage with Pytest - working-directory: "${{ github.workspace }}" - run: pytest --cov=. -v tests/ --cov-fail-under=80 + run: | + export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}" + pytest --cov=. -v tests/ --cov-fail-under=80 diff --git a/living_doc_utilities/__init__.py b/living_doc_utilities/__init__.py index e69de29..f7115cb 100644 --- a/living_doc_utilities/__init__.py +++ b/living_doc_utilities/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/living_doc_utilities/github/__init__.py b/living_doc_utilities/github/__init__.py index e69de29..f7115cb 100644 --- a/living_doc_utilities/github/__init__.py +++ b/living_doc_utilities/github/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/living_doc_utilities/inputs/__init__.py b/living_doc_utilities/inputs/__init__.py index e69de29..f7115cb 100644 --- a/living_doc_utilities/inputs/__init__.py +++ b/living_doc_utilities/inputs/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/living_doc_utilities/model/__init__.py b/living_doc_utilities/model/__init__.py index e69de29..f7115cb 100644 --- a/living_doc_utilities/model/__init__.py +++ b/living_doc_utilities/model/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# From 43bb9625309a696b749191fdf95c252e878a54a8 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:34:56 +0200 Subject: [PATCH 21/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 7040f56..6405409 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -127,5 +127,5 @@ jobs: - name: Check code coverage with Pytest run: | - export PYTHONPATH="${PYTHONPATH}:${{ github.workspace }}" + export PYTHONPATH="${{ github.workspace }}" pytest --cov=. -v tests/ --cov-fail-under=80 From 942ea3dd8f6d38a2f8237871c7996bc470d748b2 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:37:22 +0200 Subject: [PATCH 22/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 6405409..0f7b616 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -123,9 +123,8 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt - echo "${{ github.workspace }}" - name: Check code coverage with Pytest run: | - export PYTHONPATH="${{ github.workspace }}" + echo "PYTHONPATH=${{ github.workspace }}" >> $GITHUB_ENV pytest --cov=. -v tests/ --cov-fail-under=80 From fd94be4a7d69aada3ca6374c54a1eee6ee4e84bb Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:39:11 +0200 Subject: [PATCH 23/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 0f7b616..71bd0ed 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -127,4 +127,6 @@ jobs: - name: Check code coverage with Pytest run: | echo "PYTHONPATH=${{ github.workspace }}" >> $GITHUB_ENV + export pytest --cov=. -v tests/ --cov-fail-under=80 + shell: bash From 1e86ead05332929cfa26ad33af839ca937c9c10f Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:40:44 +0200 Subject: [PATCH 24/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 71bd0ed..ab6b759 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -126,7 +126,7 @@ jobs: - name: Check code coverage with Pytest run: | - echo "PYTHONPATH=${{ github.workspace }}" >> $GITHUB_ENV + export PYTHONPATH="${{ github.workspace }}" export pytest --cov=. -v tests/ --cov-fail-under=80 shell: bash From 314e10c565d270d169adcfb84087de7c66d2b998 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:44:16 +0200 Subject: [PATCH 25/33] Testing. --- tests/{living_doc_utilities => }/conftest.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{living_doc_utilities => }/conftest.py (100%) diff --git a/tests/living_doc_utilities/conftest.py b/tests/conftest.py similarity index 100% rename from tests/living_doc_utilities/conftest.py rename to tests/conftest.py From ed389e7d826c9e9d2eb5ea1f080cb95390498fc9 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:49:21 +0200 Subject: [PATCH 26/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 1 + requirements.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index ab6b759..9c86f14 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -125,6 +125,7 @@ jobs: pip install -r requirements.txt - name: Check code coverage with Pytest + working-directory: ${{ github.workspace }} run: | export PYTHONPATH="${{ github.workspace }}" export diff --git a/requirements.txt b/requirements.txt index c98affe..af42c9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ mypy==1.15.0 mypy-extensions==1.0.0 -requests==2.31.0 typing_extensions==4.12.2 PyGithub==2.3.0 pylint==3.2.6 From f0e9305c91f7e0800a6c2646fb336c2bf2f72aba Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:52:52 +0200 Subject: [PATCH 27/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index 9c86f14..f9d87cb 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -127,7 +127,10 @@ jobs: - name: Check code coverage with Pytest working-directory: ${{ github.workspace }} run: | + pwd + ls -l export PYTHONPATH="${{ github.workspace }}" export + ls -l ./${ PYTHONPATH } pytest --cov=. -v tests/ --cov-fail-under=80 shell: bash From 4e82de18c3e9d9735f911feb9048f5990cc51550 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 14:54:16 +0200 Subject: [PATCH 28/33] Testing. --- .github/workflows/static_analysis_and_tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/static_analysis_and_tests.yml b/.github/workflows/static_analysis_and_tests.yml index f9d87cb..b1d2bb2 100644 --- a/.github/workflows/static_analysis_and_tests.yml +++ b/.github/workflows/static_analysis_and_tests.yml @@ -131,6 +131,5 @@ jobs: ls -l export PYTHONPATH="${{ github.workspace }}" export - ls -l ./${ PYTHONPATH } pytest --cov=. -v tests/ --cov-fail-under=80 shell: bash From 7c14a362f02ae680442c882da22ce05c015920f9 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 15:02:03 +0200 Subject: [PATCH 29/33] Testing. --- tests/{living_doc_utilities => }/__init__.py | 0 tests/conftest.py | 2 +- tests/github/__init__.py | 15 +++++++++++++++ .../github/test_rate_limiter.py | 0 .../github/test_utils.py | 0 tests/inputs/__init__.py | 15 +++++++++++++++ .../inputs/test_action_inputs.py | 0 tests/living_doc_utilities/github/__init__.py | 0 tests/living_doc_utilities/inputs/__init__.py | 0 tests/living_doc_utilities/model/__init__.py | 0 tests/model/__init__.py | 15 +++++++++++++++ .../model/test_issue.py | 0 .../model/test_issues.py | 0 .../model/test_project_status.py | 0 .../{living_doc_utilities => }/test_decorators.py | 0 .../test_logging_config.py | 0 16 files changed, 46 insertions(+), 1 deletion(-) rename tests/{living_doc_utilities => }/__init__.py (100%) create mode 100644 tests/github/__init__.py rename tests/{living_doc_utilities => }/github/test_rate_limiter.py (100%) rename tests/{living_doc_utilities => }/github/test_utils.py (100%) create mode 100644 tests/inputs/__init__.py rename tests/{living_doc_utilities => }/inputs/test_action_inputs.py (100%) delete mode 100644 tests/living_doc_utilities/github/__init__.py delete mode 100644 tests/living_doc_utilities/inputs/__init__.py delete mode 100644 tests/living_doc_utilities/model/__init__.py create mode 100644 tests/model/__init__.py rename tests/{living_doc_utilities => }/model/test_issue.py (100%) rename tests/{living_doc_utilities => }/model/test_issues.py (100%) rename tests/{living_doc_utilities => }/model/test_project_status.py (100%) rename tests/{living_doc_utilities => }/test_decorators.py (100%) rename tests/{living_doc_utilities => }/test_logging_config.py (100%) diff --git a/tests/living_doc_utilities/__init__.py b/tests/__init__.py similarity index 100% rename from tests/living_doc_utilities/__init__.py rename to tests/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py index f228af0..65a64cc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,7 +20,7 @@ from github.Rate import Rate from github.RateLimit import RateLimit -from living_doc_utilities.github.rate_limiter import GithubRateLimiter +from tests.github import GithubRateLimiter @pytest.fixture diff --git a/tests/github/__init__.py b/tests/github/__init__.py new file mode 100644 index 0000000..f7115cb --- /dev/null +++ b/tests/github/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/living_doc_utilities/github/test_rate_limiter.py b/tests/github/test_rate_limiter.py similarity index 100% rename from tests/living_doc_utilities/github/test_rate_limiter.py rename to tests/github/test_rate_limiter.py diff --git a/tests/living_doc_utilities/github/test_utils.py b/tests/github/test_utils.py similarity index 100% rename from tests/living_doc_utilities/github/test_utils.py rename to tests/github/test_utils.py diff --git a/tests/inputs/__init__.py b/tests/inputs/__init__.py new file mode 100644 index 0000000..f7115cb --- /dev/null +++ b/tests/inputs/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/living_doc_utilities/inputs/test_action_inputs.py b/tests/inputs/test_action_inputs.py similarity index 100% rename from tests/living_doc_utilities/inputs/test_action_inputs.py rename to tests/inputs/test_action_inputs.py diff --git a/tests/living_doc_utilities/github/__init__.py b/tests/living_doc_utilities/github/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/living_doc_utilities/inputs/__init__.py b/tests/living_doc_utilities/inputs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/living_doc_utilities/model/__init__.py b/tests/living_doc_utilities/model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/model/__init__.py b/tests/model/__init__.py new file mode 100644 index 0000000..f7115cb --- /dev/null +++ b/tests/model/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/living_doc_utilities/model/test_issue.py b/tests/model/test_issue.py similarity index 100% rename from tests/living_doc_utilities/model/test_issue.py rename to tests/model/test_issue.py diff --git a/tests/living_doc_utilities/model/test_issues.py b/tests/model/test_issues.py similarity index 100% rename from tests/living_doc_utilities/model/test_issues.py rename to tests/model/test_issues.py diff --git a/tests/living_doc_utilities/model/test_project_status.py b/tests/model/test_project_status.py similarity index 100% rename from tests/living_doc_utilities/model/test_project_status.py rename to tests/model/test_project_status.py diff --git a/tests/living_doc_utilities/test_decorators.py b/tests/test_decorators.py similarity index 100% rename from tests/living_doc_utilities/test_decorators.py rename to tests/test_decorators.py diff --git a/tests/living_doc_utilities/test_logging_config.py b/tests/test_logging_config.py similarity index 100% rename from tests/living_doc_utilities/test_logging_config.py rename to tests/test_logging_config.py From 3d25634e528f49af960a087beebe3f1386e0edc8 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 15:04:15 +0200 Subject: [PATCH 30/33] Testing. --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 65a64cc..f228af0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,7 +20,7 @@ from github.Rate import Rate from github.RateLimit import RateLimit -from tests.github import GithubRateLimiter +from living_doc_utilities.github.rate_limiter import GithubRateLimiter @pytest.fixture From 0a68aaecab577508eda122902d8d5824aeceb44e Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 15:07:02 +0200 Subject: [PATCH 31/33] Testing. --- tests/inputs/test_action_inputs.py | 2 +- tests/model/test_project_status.py | 2 +- tests/test_decorators.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/inputs/test_action_inputs.py b/tests/inputs/test_action_inputs.py index 2b1f93b..db45e54 100644 --- a/tests/inputs/test_action_inputs.py +++ b/tests/inputs/test_action_inputs.py @@ -16,7 +16,7 @@ import pytest -from living_doc_utilities import BaseActionInputs +from living_doc_utilities.inputs.action_inputs import BaseActionInputs class TestActionInputs(BaseActionInputs): diff --git a/tests/model/test_project_status.py b/tests/model/test_project_status.py index 99ed249..26473e3 100644 --- a/tests/model/test_project_status.py +++ b/tests/model/test_project_status.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from living_doc_utilities import NO_PROJECT_DATA +from living_doc_utilities.constants import NO_PROJECT_DATA from living_doc_utilities.model.project_status import ProjectStatus diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 4b7bb89..a3371fc 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -17,7 +17,7 @@ from github import GithubException from requests import RequestException -from living_doc_utilities import debug_log_decorator, safe_call_decorator +from living_doc_utilities.decorators import debug_log_decorator, safe_call_decorator # sample function to be decorated From 299004b519dd3daa8462cec6c94f5df3e57b0bed Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 7 May 2025 15:09:01 +0200 Subject: [PATCH 32/33] Testing. --- tests/inputs/test_action_inputs.py | 4 ++-- tests/test_decorators.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/inputs/test_action_inputs.py b/tests/inputs/test_action_inputs.py index db45e54..8c1fa4b 100644 --- a/tests/inputs/test_action_inputs.py +++ b/tests/inputs/test_action_inputs.py @@ -34,7 +34,7 @@ def action_inputs(): def test_get_github_token(mocker, action_inputs): mock_get_action_input = mocker.patch( - "src.living_doc_utilities.inputs.action_inputs.get_action_input", + "living_doc_utilities.inputs.action_inputs.get_action_input", return_value="mock_token", ) token = action_inputs.get_github_token() @@ -54,7 +54,7 @@ def test_validate_user_configuration_failure(mocker, action_inputs): def test_print_effective_configuration(mocker, action_inputs): mock_get_action_input = mocker.patch( - "src.living_doc_utilities.inputs.action_inputs.get_action_input", + "living_doc_utilities.inputs.action_inputs.get_action_input", return_value="mock_token", ) mock_print_config = mocker.patch.object(TestActionInputs, "_print_effective_configuration") diff --git a/tests/test_decorators.py b/tests/test_decorators.py index a3371fc..0db240d 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -30,7 +30,7 @@ def sample_function(x, y): def test_debug_log_decorator(mocker): # Mock logging - mock_log_debug = mocker.patch("src.living_doc_utilities.decorators.logger.debug") + mock_log_debug = mocker.patch("living_doc_utilities.decorators.logger.debug") decorated_function = debug_log_decorator(sample_function) expected_call = [ @@ -58,7 +58,7 @@ def sample_method(x, y): def test_safe_call_decorator_network_error(rate_limiter, mocker): - mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + mock_log_error = mocker.patch("living_doc_utilities.decorators.logger.error") @safe_call_decorator(rate_limiter) def sample_method(): @@ -77,7 +77,7 @@ def sample_method(): def test_safe_call_decorator_github_api_error(rate_limiter, mocker): - mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + mock_log_error = mocker.patch("living_doc_utilities.decorators.logger.error") @safe_call_decorator(rate_limiter) def sample_method(): @@ -102,7 +102,7 @@ def sample_method(): def test_safe_call_decorator_http_error(mocker, rate_limiter): - mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + mock_log_error = mocker.patch("living_doc_utilities.decorators.logger.error") @safe_call_decorator(rate_limiter) def sample_method(): @@ -121,7 +121,7 @@ def sample_method(): def test_safe_call_decorator_exception(rate_limiter, mocker): - mock_log_error = mocker.patch("src.living_doc_utilities.decorators.logger.error") + mock_log_error = mocker.patch("living_doc_utilities.decorators.logger.error") @safe_call_decorator(rate_limiter) def sample_method(x, y): From dda4e831db7ba1e482e5da66494b049e1a04fd63 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Mon, 12 May 2025 13:40:42 +0200 Subject: [PATCH 33/33] Add new config files improving the project. --- .editorconfig | 39 +++++++++++++++++++++++++++++++++++++++ .gitattributes | 28 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ad18e4e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true + +[*.xml] +indent_size = 4 +indent_style = space +insert_final_newline = true + +[*.{java,scala,js,json,css}] +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 120 + +[*.md] +trim_trailing_whitespace = false + +[*.{cmd,bat}] +end_of_line = crlf +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..06d0cbe --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +# +# Copyright 2025 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################### +# Git Line Endings # +############################### + +# Set default behaviour to automatically normalize line endings. +* text=auto + +# Force the following filetypes to have unix eols, Windows can usually handle it well +*.* text eol=lf + +# Force batch scripts to always use CRLF line endings as they in some cases might not work correctly. +# Also if a repo is accessed in Windows via a file share from Linux, the scripts will work too +*.cmd text eol=crlf +*.bat text eol=crlf