From 0753d51e0419cfe74623bb55a70cacfe4f64d293 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 10:51:58 +0200 Subject: [PATCH 1/7] Upgrade project structure so a package can be build --- .dockerignore | 1 + Dockerfile | 1 + main.py | 15 +- pyproject.toml | 5 + src-docs/__init__.py.md | 100 --- src-docs/action.py.md | 60 -- src-docs/check.py.md | 117 ---- src-docs/clients.py.md | 44 -- src-docs/commit.py.md | 67 -- src-docs/constants.py.md | 21 - src-docs/content.py.md | 91 --- src-docs/discourse.py.md | 300 --------- src-docs/docs_directory.py.md | 85 --- src-docs/download.py.md | 33 - src-docs/exceptions.py.md | 135 ----- src-docs/index.py.md | 162 ----- src-docs/metadata.py.md | 49 -- src-docs/migration.py.md | 70 --- src-docs/navigation_table.py.md | 59 -- src-docs/reconcile.py.md | 108 ---- src-docs/repository.py.md | 573 ------------------ src-docs/sort.py.md | 40 -- src-docs/types_.py.md | 480 --------------- src/{ => gatekeeper}/__init__.py | 22 +- src/{ => gatekeeper}/action.py | 4 +- src/{ => gatekeeper}/check.py | 6 +- src/{ => gatekeeper}/clients.py | 8 +- src/{ => gatekeeper}/commit.py | 0 src/{ => gatekeeper}/constants.py | 0 src/{ => gatekeeper}/content.py | 2 +- src/{ => gatekeeper}/discourse.py | 2 +- src/{ => gatekeeper}/docs_directory.py | 4 +- src/{ => gatekeeper}/download.py | 10 +- src/{ => gatekeeper}/exceptions.py | 0 src/{ => gatekeeper}/index.py | 12 +- src/{ => gatekeeper}/metadata.py | 4 +- src/{ => gatekeeper}/migration.py | 4 +- src/{ => gatekeeper}/navigation_table.py | 6 +- src/{ => gatekeeper}/reconcile.py | 12 +- src/{ => gatekeeper}/repository.py | 12 +- src/{ => gatekeeper}/sort.py | 2 +- src/{ => gatekeeper}/types_.py | 2 +- tests/conftest.py | 10 +- tests/factories.py | 4 +- tests/integration/conftest.py | 2 +- .../integration/test___init__run_conflict.py | 6 +- tests/integration/test___init__run_migrate.py | 12 +- .../integration/test___init__run_reconcile.py | 8 +- tests/integration/test_discourse.py | 4 +- tests/unit/action/test_other_actions.py | 4 +- tests/unit/action/test_update_action.py | 4 +- tests/unit/conftest.py | 4 +- tests/unit/helpers.py | 4 +- tests/unit/test___init__.py | 60 +- tests/unit/test_check.py | 2 +- tests/unit/test_commit.py | 6 +- tests/unit/test_content.py | 2 +- tests/unit/test_discourse.py | 4 +- tests/unit/test_docs_directory.py | 2 +- tests/unit/test_download.py | 6 +- tests/unit/test_index.py | 4 +- tests/unit/test_index_contents_get.py | 2 +- tests/unit/test_index_contents_hierarchy.py | 2 +- tests/unit/test_index_contents_parse.py | 2 +- tests/unit/test_metadata.py | 2 +- tests/unit/test_migration/test_private.py | 2 +- tests/unit/test_migration/test_public.py | 2 +- tests/unit/test_navigation_table.py | 4 +- tests/unit/test_reconcile.py | 20 +- tests/unit/test_repository.py | 8 +- tests/unit/test_sort.py | 2 +- tests/unit/test_types_.py | 2 +- 72 files changed, 175 insertions(+), 2749 deletions(-) delete mode 100644 src-docs/__init__.py.md delete mode 100644 src-docs/action.py.md delete mode 100644 src-docs/check.py.md delete mode 100644 src-docs/clients.py.md delete mode 100644 src-docs/commit.py.md delete mode 100644 src-docs/constants.py.md delete mode 100644 src-docs/content.py.md delete mode 100644 src-docs/discourse.py.md delete mode 100644 src-docs/docs_directory.py.md delete mode 100644 src-docs/download.py.md delete mode 100644 src-docs/exceptions.py.md delete mode 100644 src-docs/index.py.md delete mode 100644 src-docs/metadata.py.md delete mode 100644 src-docs/migration.py.md delete mode 100644 src-docs/navigation_table.py.md delete mode 100644 src-docs/reconcile.py.md delete mode 100644 src-docs/repository.py.md delete mode 100644 src-docs/sort.py.md delete mode 100644 src-docs/types_.py.md rename src/{ => gatekeeper}/__init__.py (94%) rename src/{ => gatekeeper}/action.py (99%) rename src/{ => gatekeeper}/check.py (98%) rename src/{ => gatekeeper}/clients.py (84%) rename src/{ => gatekeeper}/commit.py (100%) rename src/{ => gatekeeper}/constants.py (100%) rename src/{ => gatekeeper}/content.py (98%) rename src/{ => gatekeeper}/discourse.py (99%) rename src/{ => gatekeeper}/docs_directory.py (98%) rename src/{ => gatekeeper}/download.py (85%) rename src/{ => gatekeeper}/exceptions.py (100%) rename src/{ => gatekeeper}/index.py (97%) rename src/{ => gatekeeper}/metadata.py (98%) rename src/{ => gatekeeper}/migration.py (99%) rename src/{ => gatekeeper}/navigation_table.py (98%) rename src/{ => gatekeeper}/reconcile.py (98%) rename src/{ => gatekeeper}/repository.py (99%) rename src/{ => gatekeeper}/sort.py (99%) rename src/{ => gatekeeper}/types_.py (99%) diff --git a/.dockerignore b/.dockerignore index cef48cf6..faed0abb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ !/main.py !/requirements.txt !/src +!/pyproject.toml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index eb678b66..abe98b97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ COPY requirements.txt /usr/src/app RUN pip install --no-cache-dir -r requirements.txt COPY . /usr/src/app +RUN pip install /usr/src/app ENV PYTHONPATH /usr/src/app CMD ["/usr/src/app/main.py"] diff --git a/main.py b/main.py index 7e8088a8..e3162b7a 100755 --- a/main.py +++ b/main.py @@ -16,10 +16,17 @@ from functools import partial from pathlib import Path -from src import GETTING_STARTED, exceptions, pre_flight_checks, run_migrate, run_reconcile, types_ -from src.clients import get_clients -from src.constants import DEFAULT_BRANCH -from src.types_ import ActionResult, PullRequestAction +from gatekeeper import ( + GETTING_STARTED, + exceptions, + pre_flight_checks, + run_migrate, + run_reconcile, + types_, +) +from gatekeeper.clients import get_clients +from gatekeeper.constants import DEFAULT_BRANCH +from gatekeeper.types_ import ActionResult, PullRequestAction GITHUB_HEAD_REF_ENV_NAME = "GITHUB_HEAD_REF" GITHUB_OUTPUT_ENV_NAME = "GITHUB_OUTPUT" diff --git a/pyproject.toml b/pyproject.toml index 9729678f..6fcd4f99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,11 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. +[project] +name = "discourse-gatekeeper" +version = "0.0.1" + + [tool.bandit] exclude_dirs = ["/venv/", ".tox"] [tool.bandit.assert_used] diff --git a/src-docs/__init__.py.md b/src-docs/__init__.py.md deleted file mode 100644 index 1c2b6a36..00000000 --- a/src-docs/__init__.py.md +++ /dev/null @@ -1,100 +0,0 @@ - - - - -# module `__init__.py` -Library for uploading docs to charmhub. - -**Global Variables** ---------------- -- **DRY_RUN_NAVLINK_LINK** -- **FAIL_NAVLINK_LINK** -- **DOCUMENTATION_TAG** -- **DEFAULT_BRANCH_NAME** -- **GETTING_STARTED** - ---- - - - -## function `run_reconcile` - -```python -run_reconcile( - clients: Clients, - user_inputs: UserInputs -) → ReconcileOutputs | None -``` - -Upload the documentation to charmhub. - - - -**Args:** - - - `clients`: The clients to interact with things like discourse and the repository. - - `user_inputs`: Configurable inputs for running discourse-gatekeeper. - - - -**Returns:** - ReconcileOutputs object with the result of the action. None, if there is no reconcile. - - - -**Raises:** - - - `InputError`: if there are any problems with the contents index or executing any of the actions. - - `TaggingNotAllowedError`: if the reconcile tries to tag a branch which is not the main base branch - - ---- - - - -## function `run_migrate` - -```python -run_migrate(clients: Clients, user_inputs: UserInputs) → MigrateOutputs | None -``` - -Migrate existing docs from charmhub to local repository. - - - -**Args:** - - - `clients`: The clients to interact with things like discourse and the repository. - - `user_inputs`: Configurable inputs for running discourse-gatekeeper. - - - -**Returns:** - MigrateOutputs providing details on the action performed and a link to the Pull Request containing migrated documentation. None if there is no migration. - - ---- - - - -## function `pre_flight_checks` - -```python -pre_flight_checks(clients: Clients, user_inputs: UserInputs) → bool -``` - -Perform checks to make sure the repository is in a consistent state. - - - -**Args:** - - - `clients`: The clients to interact with things like discourse and the repository. - - `user_inputs`: Configurable inputs for running discourse-gatekeeper. - - - -**Returns:** - Boolean representing whether the checks have all been passed. - - diff --git a/src-docs/action.py.md b/src-docs/action.py.md deleted file mode 100644 index 6444b189..00000000 --- a/src-docs/action.py.md +++ /dev/null @@ -1,60 +0,0 @@ - - - - -# module `action.py` -Module for taking the required actions to match the server state with the local state. - -**Global Variables** ---------------- -- **DRY_RUN_NAVLINK_LINK** -- **DRY_RUN_REASON** -- **BASE_MISSING_REASON** -- **FAIL_NAVLINK_LINK** -- **NOT_DELETE_REASON** - ---- - - - -## function `run_all` - -```python -run_all( - actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction], - index: Index, - discourse: Discourse, - dry_run: bool, - delete_pages: bool -) → tuple[str, list[ActionReport]] -``` - -Take the actions against the server. - - - -**Args:** - - - `actions`: The actions to take. - - `index`: Information about the index. - - `discourse`: A client to the documentation server. - - `dry_run`: If enabled, only log the action that would be taken. - - `delete_pages`: Whether to delete pages that are no longer needed. - - - -**Returns:** - A 2-element tuple with the index url and the reports of all the requested action. - - ---- - -## class `UpdateCase` -The possible cases for the update action. - -Attrs: DRY_RUN: Do not make any changes. CONTENT_CHANGE: The content has been changed. BASE_MISSING: The base content is not available. DEFAULT: No other specific case applies. - - - - - diff --git a/src-docs/check.py.md b/src-docs/check.py.md deleted file mode 100644 index e1b3babf..00000000 --- a/src-docs/check.py.md +++ /dev/null @@ -1,117 +0,0 @@ - - - - -# module `check.py` -Module for running checks. - -**Global Variables** ---------------- -- **DOCUMENTATION_TAG** - ---- - - - -## function `get_path_with_diffs` - -```python -get_path_with_diffs( - actions: Iterable[UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction] -) → PathsWithDiff -``` - -Generate the paths that have either local or server content changes. - - - -**Args:** - - - `actions`: The update actions to track diffs for. - - - -**Returns:** - The paths that have differences. - - ---- - - - -## function `conflicts` - -```python -conflicts( - actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] -) → Iterator[Problem] -``` - -Check whether actions have any content conflicts. - -There are two types of conflicts. The first is where the local content is different to what is on the server and both the local content and the server content is different from the base. This means that there were edits on the server which have not been merged into git and the PR is making changes to the same page. - -The second type of conflict is a logical conflict which arises out of that there are at least some changes on the server that have not been merged into git yet and the branch is proposing to make changes to the documentation as well. This means that there could be changes made on the server which logically conflict with proposed changes in the PR. These conflicts can be supppressed using the discourse-ahead-ok tag on the commit that the action is running on. - - - -**Args:** - - - `actions`: The actions to check. - - - -**Yields:** - A problem for each action with a conflict - - ---- - - - -## function `external_refs` - -```python -external_refs( - index_contents: Iterable[IndexContentsListItem] -) → Iterator[Problem] -``` - -Check whether external references are valid. - -This check sends a HEAD requests and checks for a 2XX response after any redirects. - - - -**Args:** - - - `index_contents`: The contents list items to check. - - - -**Yields:** - A problem for each list item with an invalid external reference. - - ---- - -## class `PathsWithDiff` -Keeps track of paths that have any differences. - -Attrs: base_local_diffs: The paths that have a difference between the base and local content. base_server_diffs: The paths that have a difference between the local and server content. - - - - - ---- - -## class `Problem` -Details about a failed check. - -Attrs: path: Unique identifier for the file and discourse topic with the problem description: A summary of what the problem is and how to resolve it. - - - - - diff --git a/src-docs/clients.py.md b/src-docs/clients.py.md deleted file mode 100644 index 74b77615..00000000 --- a/src-docs/clients.py.md +++ /dev/null @@ -1,44 +0,0 @@ - - - - -# module `clients.py` -Module for Client class. - - ---- - - - -## function `get_clients` - -```python -get_clients(user_inputs: UserInputs, base_path: Path) → Clients -``` - -Return Clients object. - - - -**Args:** - - - `user_inputs`: inputs provided via environment - - `base_path`: path where the git repository is stored - - - -**Returns:** - Clients object embedding both Discourse API and Repository clients - - ---- - -## class `Clients` -Collection of clients needed during execution. - -Attrs: discourse: Discourse client. repository: Client for the repository. - - - - - diff --git a/src-docs/commit.py.md b/src-docs/commit.py.md deleted file mode 100644 index b6f93157..00000000 --- a/src-docs/commit.py.md +++ /dev/null @@ -1,67 +0,0 @@ - - - - -# module `commit.py` -Module for handling interactions with git commit. - - ---- - - - -## function `parse_git_show` - -```python -parse_git_show( - output: str, - repository_path: Path -) → Iterator[FileAddedOrModified | FileDeleted] -``` - -Parse the output of a git show with --name-status into manageable data. - - - -**Args:** - - - `output`: The output of the git show command. - - `repository_path`: The path to the git repository. - - - -**Yields:** - Information about each of the files that changed in the commit. - - ---- - -## class `FileAddedOrModified` -File that was added, mofied or copied copied in a commit. - - - -**Attributes:** - - - `path`: The location of the file on disk. - - `content`: The content of the file. - - - - - ---- - -## class `FileDeleted` -File that was deleted in a commit. - - - -**Attributes:** - - - `path`: The location of the file on disk. - - - - - diff --git a/src-docs/constants.py.md b/src-docs/constants.py.md deleted file mode 100644 index 3c950b6b..00000000 --- a/src-docs/constants.py.md +++ /dev/null @@ -1,21 +0,0 @@ - - - - -# module `constants.py` -Shared constants. - -The use of this module should be limited to cases where the constant is not better placed in another module or to resolve circular imports. - -**Global Variables** ---------------- -- **DEFAULT_BRANCH** -- **DOCUMENTATION_TAG** -- **DOCUMENTATION_FOLDER_NAME** -- **DOC_FILE_EXTENSION** -- **DOCUMENTATION_INDEX_FILENAME** -- **NAVIGATION_HEADING** -- **NAVIGATION_TABLE_START** -- **PATH_CHARS** - - diff --git a/src-docs/content.py.md b/src-docs/content.py.md deleted file mode 100644 index 9e0b65b1..00000000 --- a/src-docs/content.py.md +++ /dev/null @@ -1,91 +0,0 @@ - - - - -# module `content.py` -Module for checking conflicts using 3-way merge and create content based on a 3 way merge. - - ---- - - - -## function `conflicts` - -```python -conflicts(base: str, theirs: str, ours: str) → str | None -``` - -Check for merge conflicts based on the git merge algorithm. - - - -**Args:** - - - `base`: The starting point for both changes. - - `theirs`: The other change. - - `ours`: The local change. - - - -**Returns:** - The description of the merge conflicts or None if there are no conflicts. - - ---- - - - -## function `merge` - -```python -merge(base: str, theirs: str, ours: str) → str -``` - -Create the merged content based on the git merge algorithm. - - - -**Args:** - - - `base`: The starting point for both changes. - - `theirs`: The other change. - - `ours`: The local change. - - - -**Returns:** - The merged content. - - - -**Raises:** - - - `ContentError`: if there are merge conflicts. - - ---- - - - -## function `diff` - -```python -diff(first: str, second: str) → str -``` - -Show the difference between two strings. - - - -**Args:** - - - `first`: One of the strings to compare. - - `second`: One of the strings to compare. - - - -**Returns:** - The diff between the two strings. - - diff --git a/src-docs/discourse.py.md b/src-docs/discourse.py.md deleted file mode 100644 index cfd63c2a..00000000 --- a/src-docs/discourse.py.md +++ /dev/null @@ -1,300 +0,0 @@ - - - - -# module `discourse.py` -Interface for Discourse interactions. - - ---- - - - -## function `create_discourse` - -```python -create_discourse( - hostname: str, - category_id: str, - api_username: str, - api_key: str -) → Discourse -``` - -Create discourse client. - - - -**Args:** - - - `hostname`: The Discourse server hostname. - - `category_id`: The category to use for topics. - - `api_username`: The discourse API username to use for interactions with the server. - - `api_key`: The discourse API key to use for interactions with the server. - - - -**Returns:** - A discourse client that is connected to the server. - - - -**Raises:** - InputError: if the api_username and api_key arguments are not strings or empty, if the protocol has been included in the hostname, the hostname is not a string or the category_id is not an integer or a string that can be converted to an integer. - - ---- - -## class `Discourse` -Interact with a discourse server. - -Attrs: host: The host of the discourse server. - - - -### function `__init__` - -```python -__init__(host: str, api_username: str, api_key: str, category_id: int) → None -``` - -Construct. - - - -**Args:** - - - `host`: The HTTP protocol and hostname for discourse (e.g., https://discourse). - - `api_username`: The username to use for API requests. - - `api_key`: The API key for requests. - - `category_id`: The category identifier to put the topics into. - - ---- - -#### property host - -The HTTP protocol and hostname for discourse (e.g., https://discourse). - - - ---- - - - -### function `absolute_url` - -```python -absolute_url(url: str) → str -``` - -Get the URL including base path for a topic. - - - -**Args:** - - - `url`: The relative or absolute URL. - - - -**Returns:** - The url with the base path. - ---- - - - -### function `check_topic_read_permission` - -```python -check_topic_read_permission(url: str) → bool -``` - -Check whether the credentials have read permission on a topic. - -Uses whether retrieve topic succeeds as indication whether the read permission is available. - - - -**Args:** - - - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. - - - -**Returns:** - Whether the credentials have read permissions to the topic. - ---- - - - -### function `check_topic_write_permission` - -```python -check_topic_write_permission(url: str) → bool -``` - -Check whether the credentials have write permission on a topic. - - - -**Args:** - - - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. - - - -**Returns:** - Whether the credentials have write permissions to the topic. - ---- - - - -### function `create_topic` - -```python -create_topic(title: str, content: str) → str -``` - -Create a new topic. - - - -**Args:** - - - `title`: The title of the topic. - - `content`: The content for the first post in the topic. - - - -**Returns:** - The URL to the topic. - - - -**Raises:** - - - `DiscourseError`: if anything goes wrong during topic creation. - ---- - - - -### function `delete_topic` - -```python -delete_topic(url: str) → str -``` - -Delete a topic. - - - -**Args:** - - - `url`: The URL to the topic. - - - -**Returns:** - The link to the deleted topic. - - - -**Raises:** - - - `DiscourseError`: if authentication fails if the server refuses to delete the topic, if the topic is not found or if anything else has gone wrong. - ---- - - - -### function `retrieve_topic` - -```python -retrieve_topic(url: str) → str -``` - -Retrieve the topic content. - - - -**Args:** - - - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. - - - -**Returns:** - The content of the first post in the topic. - - - -**Raises:** - - - `DiscourseError`: if authentication fails, if the server refuses to return the requested topic or if the topic is not found. - ---- - - - -### function `topic_url_valid` - -```python -topic_url_valid(url: str) → _ValidationResultValid | _ValidationResultInvalid -``` - -Check whether a url to a topic is valid. Assume the url is well formatted. - -Validations: 1. The URL must start with the base path configured during construction. 2. The URL must resolve on a discourse HEAD request. 3. The URL must have 3 components in its path. 4. The first component in the path must be the literal 't'. 5. The second component in the path must be the slug to the topic which must have at least 1 character. 6. The third component must the the topic id as an integer. - - - -**Args:** - - - `url`: The URL to check. - - - -**Returns:** - Whether the URL is a valid topic URL. - ---- - - - -### function `update_topic` - -```python -update_topic( - url: str, - content: str, - edit_reason: str = 'Charm documentation updated' -) → str -``` - -Update the first post of a topic. - - - -**Args:** - - - `url`: The URL to the topic. - - `content`: The content for the first post in the topic. - - `edit_reason`: The reason the edit was made. - - - -**Returns:** - The link to the updated topic. - - - -**Raises:** - - - `DiscourseError`: if authentication fails, if the server refuses to update the first post in the topic or if the topic is not found. - - diff --git a/src-docs/docs_directory.py.md b/src-docs/docs_directory.py.md deleted file mode 100644 index 77ca760c..00000000 --- a/src-docs/docs_directory.py.md +++ /dev/null @@ -1,85 +0,0 @@ - - - - -# module `docs_directory.py` -Class for reading the docs directory. - -**Global Variables** ---------------- -- **DOC_FILE_EXTENSION** - ---- - - - -## function `calculate_table_path` - -```python -calculate_table_path(path_relative_to_docs: Path) → tuple[str, ] -``` - -Calculate the table path of a path. - - - -**Args:** - - - `path_relative_to_docs`: The path to calculate the table path for relative to the docs directory. - - - -**Returns:** - The relative path to the docs directory, replacing / with -, removing the extension and converting to lower case. - - ---- - - - -## function `read` - -```python -read(docs_path: Path) → Iterator[PathInfo] -``` - -Read the docs directory and return information about each directory and documentation file. - -Algorithm: 1. Get a list of all sub directories and .md files in the docs folder. 2. For each directory/ file: 2.1. Calculate the level based on the number of sub-directories to the docs directory including the docs directory. 2.2. Calculate the table path using the relative path to the docs directory, replacing / with -, removing the extension and converting to lower case. 2.3. Calculate the navlink title based on the first heading, first line if there is no heading or the file/ directory name excluding the extension with - replaced by space and titlelized if the file is empty or it is a directory. - - - -**Args:** - - - `docs_path`: The path to the docs directory containing all the documentation. - - - -**Returns:** - Information about each directory and documentation file in the docs folder. - - ---- - - - -## function `has_docs_directory` - -```python -has_docs_directory(docs_path: Path) → bool -``` - -Return existence of docs directory from base path. - - - -**Args:** - - - `docs_path`: Docs path of the repository where docs are - - - -**Returns:** - True if documentation folder exists, False otherwise - - diff --git a/src-docs/download.py.md b/src-docs/download.py.md deleted file mode 100644 index 057eb2f7..00000000 --- a/src-docs/download.py.md +++ /dev/null @@ -1,33 +0,0 @@ - - - - -# module `download.py` -Library for downloading docs folder from charmhub. - - ---- - - - -## function `recreate_docs` - -```python -recreate_docs(clients: Clients, base: str) → bool -``` - -Recreate the docs folder and checks whether the docs folder is aligned with base branch/tag. - - - -**Args:** - - - `clients`: Clients object containing Repository and Discourse API clients - - `base`: tag to be compared to - - - -**Returns:** - boolean representing whether any differences have occurred - - diff --git a/src-docs/exceptions.py.md b/src-docs/exceptions.py.md deleted file mode 100644 index 641f7a51..00000000 --- a/src-docs/exceptions.py.md +++ /dev/null @@ -1,135 +0,0 @@ - - - - -# module `exceptions.py` -Exceptions for uploading docs to charmhub. - - - ---- - -## class `ActionError` -A problem with the taking an action occurred. - - - - - ---- - -## class `BaseError` -All raised exceptions inherit from this one. - - - - - ---- - -## class `ContentError` -A problem with the content occurred. - - - - - ---- - -## class `DiscourseError` -Parent exception for all Discourse errors. - - - - - ---- - -## class `InputError` -A problem with the user input occurred. - - - - - ---- - -## class `MigrationError` -A problem with migration occurred. - - - - - ---- - -## class `NavigationTableParseError` -A problem with the navigation table parsing occurred. - - - - - ---- - -## class `PagePermissionError` -A required permission is not available on a page. - - - - - ---- - -## class `ReconcilliationError` -A problem with the reconcilliation occurred. - - - - - ---- - -## class `RepositoryClientError` -A problem with git repository client occurred. - - - - - ---- - -## class `RepositoryFileNotFoundError` -A problem retrieving a file from a git repository occurred. - - - - - ---- - -## class `RepositoryTagNotFoundError` -A problem retrieving a tag from a git repository occurred. - - - - - ---- - -## class `ServerError` -A problem with the server storing the documentation occurred. - - - - - ---- - -## class `TaggingNotAllowedError` -The commit cannot be tagged as it is outside of the main. - - - - - diff --git a/src-docs/index.py.md b/src-docs/index.py.md deleted file mode 100644 index 96ddb6fd..00000000 --- a/src-docs/index.py.md +++ /dev/null @@ -1,162 +0,0 @@ - - - - -# module `index.py` -Execute the uploading of documentation. - -**Global Variables** ---------------- -- **DOC_FILE_EXTENSION** -- **DOCUMENTATION_INDEX_FILENAME** -- **NAVIGATION_HEADING** -- **CONTENTS_HEADER** -- **CONTENTS_END_LINE_PREFIX** - ---- - - - -## function `get` - -```python -get(metadata: Metadata, docs_path: Path, server_client: Discourse) → Index -``` - -Retrieve the local and server index information. - - - -**Args:** - - - `metadata`: Information about the charm. - - `docs_path`: The base path to look for the documentation. - - `server_client`: A client to the documentation server. - - - -**Returns:** - The index page. - - - -**Raises:** - - - `ServerError`: if interactions with the documentation server occurs. - - ---- - - - -## function `contents_from_page` - -```python -contents_from_page(page: str) → str -``` - -Get index file contents from server page. - - - -**Args:** - - - `page`: Page contents from server. - - - -**Returns:** - Index file contents. - - ---- - - - -## function `get_content_for_server` - -```python -get_content_for_server(index_file: IndexFile) → str -``` - -Get the contents from the index file that should be passed to the server. - - - -**Args:** - - - `index_file`: Information about the local index file. - - - -**Returns:** - The contents of the index file that should be stored on the server. - - ---- - - - -## function `classify_item_reference` - -```python -classify_item_reference( - reference: str, - docs_path: Path -) → -``` - -Classify the type of a reference. - - - -**Args:** - - - `reference`: The reference to classify. - - `docs_path`: The parent path of the reference. - - - -**Returns:** - The type of the reference. - - ---- - - - -## function `get_contents` - -```python -get_contents( - index_file: IndexFile, - docs_path: Path -) → Iterator[IndexContentsListItem] -``` - -Get the contents list items from the index file. - - - -**Args:** - - - `index_file`: The index file to read the contents from. - - `docs_path`: The base directory of all items. - - - -**Returns:** - Iterator with all items from the contents list. - - ---- - -## class `ItemReferenceType` -Classification for the path of an item. - -Attrs: EXTERNAL: a link to an external resource. DIR: a link to a local directory. FILE: a link to a local file. UNKNOWN: The reference is not a known type. - - - - - diff --git a/src-docs/metadata.py.md b/src-docs/metadata.py.md deleted file mode 100644 index 4bf6d711..00000000 --- a/src-docs/metadata.py.md +++ /dev/null @@ -1,49 +0,0 @@ - - - - -# module `metadata.py` -Module for parsing metadata.yaml file. - -**Global Variables** ---------------- -- **CHARMCRAFT_FILENAME** -- **CHARMCRAFT_NAME_KEY** -- **CHARMCRAFT_LINKS_KEY** -- **CHARMCRAFT_LINKS_DOCS_KEY** -- **METADATA_DOCS_KEY** -- **METADATA_FILENAME** -- **METADATA_NAME_KEY** - ---- - - - -## function `get` - -```python -get(path: Path) → Metadata -``` - -Check for and read the metadata. - -The charm metadata can be in the file metadata.yaml or in charmcraft.yaml. From charmcraft version 2.5, the information should be in charmcraft.yaml, and the user should only modify that file. This function does not consider the case in which the name is in one file and the doc link is in the other. - - - -**Args:** - - - `path`: The base path to look for the metadata files. - - - -**Returns:** - The contents of the metadata file. - - - -**Raises:** - - - `InputError`: if the metadata file does not exist or is malformed. - - diff --git a/src-docs/migration.py.md b/src-docs/migration.py.md deleted file mode 100644 index 3784ad2f..00000000 --- a/src-docs/migration.py.md +++ /dev/null @@ -1,70 +0,0 @@ - - - - -# module `migration.py` -Module for migrating remote documentation into local git repository. - -**Global Variables** ---------------- -- **EMPTY_DIR_REASON** -- **GITKEEP_FILENAME** - ---- - - - -## function `make_parent` - -```python -make_parent(docs_path: Path, document_meta: MigrationFileMeta) → Path -``` - -Construct path leading to document to be created. - - - -**Args:** - - - `docs_path`: Path to documentation directory. - - `document_meta`: Information about document to be migrated. - - - -**Returns:** - Full path to the parent directory of the document to be migrated. - - ---- - - - -## function `run` - -```python -run( - table_rows: Iterable[TableRow], - index_content: str, - discourse: Discourse, - docs_path: Path -) → None -``` - -Write table contents to the document directory. - - - -**Args:** - - - `table_rows`: Iterable sequence of documentation structure to be migrated. - - `index_content`: Main content describing the charm. - - `discourse`: Client to the documentation server. - - `docs_path`: The path to the docs directory containing all the documentation. - - - -**Raises:** - - - `MigrationError`: if any migration report has failed. - - diff --git a/src-docs/navigation_table.py.md b/src-docs/navigation_table.py.md deleted file mode 100644 index 1d2956e4..00000000 --- a/src-docs/navigation_table.py.md +++ /dev/null @@ -1,59 +0,0 @@ - - - - -# module `navigation_table.py` -Module for parsing and rendering a navigation table. - - ---- - - - -## function `from_page` - -```python -from_page(page: str, discourse: Discourse) → Iterator[TableRow] -``` - -Create an instance based on a markdown page. - -Algorithm: 1. Extract the table based on a regular expression looking for a 3 column table with the headers level, path and navlink (case insensitive). If the table is not found, assume that it is equivalent to a table without rows. 2. Process the rows line by line: 2.1. If the row matches the header or filler pattern, skip it. 2.2. Extract the level, path and navlink values. - - - -**Args:** - - - `page`: The page to extract the rows from. - - `discourse`: API to the Discourse server. - - - -**Returns:** - The parsed rows from the table. - - ---- - - - -## function `generate_table_row` - -```python -generate_table_row(lines: Sequence[str]) → Iterator[TableRow] -``` - -Return an iterator with the TableRows representing the parsed table lines. - - - -**Args:** - - - `lines`: list of strings representing the different lines. - - - -**Yields:** - parsed TableRow object, representing the row of the table - - diff --git a/src-docs/reconcile.py.md b/src-docs/reconcile.py.md deleted file mode 100644 index 1150018f..00000000 --- a/src-docs/reconcile.py.md +++ /dev/null @@ -1,108 +0,0 @@ - - - - -# module `reconcile.py` -Module for calculating required changes based on docs directory and navigation table. - -**Global Variables** ---------------- -- **DOCUMENTATION_TAG** -- **NAVIGATION_TABLE_START** - ---- - - - -## function `is_same_content` - -```python -is_same_content( - index: Index, - actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] -) → bool -``` - -Check if the content on Discourse and Github matches. - - - -**Args:** - - - `index`: Index object representing local and server content for the index file - - `actions`: List of actions representing what the reconcile action would do over all topics - - - -**Returns:** - Boolean true if the contents match, false otherwise - - ---- - - - -## function `run` - -```python -run( - sorted_path_infos: Iterable[PathInfo | IndexContentsListItem], - table_rows: Iterable[TableRow], - clients: Clients, - base_path: Path -) → Iterator[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] -``` - -Reconcile differences between the docs directory and documentation server. - -Preserves the order of path_infos although does not for items only in table_rows. - -This function needs to match files and directories locally to items on the navigation table on the server knowing that there may be cases that are not matched. The navigation table relies on the order that items are displayed to figure out the hierarchy/ page grouping (this is not a design choice of this action but how the documentation is interpreted by charmhub). Assume the `path_infos` have been sorted to ensure that the hierarchy will be calculated correctly by the server when the new navigation table is generated. - -Items only in table_rows won't have their order preserved. Those items are the items that are only on the server, i.e., those keys will just result in delete actions which have no effect on the navigation table that is generated and hence ordering for them doesn't matter. - - - -**Args:** - - - `base_path`: The base path of the repository. - - `sorted_path_infos`: Information about the local documentation files. - - `table_rows`: Rows from the navigation table. - - `clients`: The clients to interact with things like discourse and the repository. - - - -**Returns:** - The actions required to reconcile differences between the documentation server and local files. - - ---- - - - -## function `index_page` - -```python -index_page( - index: Index, - table_rows: Iterable[TableRow], - discourse: Discourse -) → CreateIndexAction | NoopIndexAction | UpdateIndexAction -``` - -Reconcile differences for the index page. - - - -**Args:** - - - `index`: Information about the index on the server and locally. - - `table_rows`: The current navigation table rows based on local files. - - `discourse`: A client to the documentation server. - - - -**Returns:** - The action to take for the index page. - - diff --git a/src-docs/repository.py.md b/src-docs/repository.py.md deleted file mode 100644 index 92e31170..00000000 --- a/src-docs/repository.py.md +++ /dev/null @@ -1,573 +0,0 @@ - - - - -# module `repository.py` -Module for handling interactions with git repository. - -**Global Variables** ---------------- -- **DOCUMENTATION_FOLDER_NAME** -- **GITHUB_HOSTNAME** -- **ORIGIN_NAME** -- **ACTIONS_USER_NAME** -- **ACTIONS_USER_EMAIL** -- **ACTIONS_PULL_REQUEST_TITLE** -- **ACTIONS_PULL_REQUEST_BODY** -- **PR_LINK_NO_CHANGE** -- **TAG_MESSAGE** -- **CONFIG_USER_SECTION_NAME** -- **CONFIG_USER_NAME** -- **CONFIG_USER_EMAIL** -- **BRANCH_PREFIX** -- **DEFAULT_BRANCH_NAME** -- **ACTIONS_COMMIT_MESSAGE** - ---- - - - -## function `create_repository_client` - -```python -create_repository_client( - access_token: str | None, - base_path: Path, - charm_dir: str = '' -) → Client -``` - -Create a Github instance to handle communication with Github server. - - - -**Args:** - - - `access_token`: Access token that has permissions to open a pull request. - - `base_path`: Path where local .git resides in. - - `charm_dir`: Relative directory where the charm files are located. - - - -**Raises:** - - - `InputError`: if invalid access token or invalid git remote URL is provided. - - - -**Returns:** - A Github repository instance. - - ---- - -## class `Client` -Wrapper for git/git-server related functionalities. - -Attrs: base_path: The root directory of the repository. base_charm_path: The directory of the repository where the charm is. docs_path: The directory of the repository where the documentation is. metadata: Metadata object of the charm has_docs_directory: whether the repository has a docs directory current_branch: current git branch used in the repository current_commit: current commit checkout in the repository branches: list of all branches - - - -### function `__init__` - -```python -__init__( - repository: Repo, - github_repository: Repository, - charm_dir: str = '' -) → None -``` - -Construct. - - - -**Args:** - - - `repository`: Client for interacting with local git repository. - - `github_repository`: Client for interacting with remote github repository. - - `charm_dir`: Relative directory where charm files are located. - - ---- - -#### property base_charm_path - -Return the Path of the charm in the repository. - - - -**Returns:** - Path of the repository. - ---- - -#### property branches - -Return all local branches. - ---- - -#### property current_branch - -Return the current branch. - ---- - -#### property current_commit - -Return the current branch. - ---- - -#### property docs_path - -Return the Path of the charm in the repository. - - - -**Returns:** - Path of the repository. - ---- - -#### property has_docs_directory - -Return whether the repository has a docs directory. - ---- - -#### property metadata - -Return the Metadata object of the charm. - - - ---- - - - -### function `create_branch` - -```python -create_branch(branch_name: str, base: str | None = None) → Client -``` - -Create a new branch. - -Note that this will not switch branch. To create and switch branch, please pipe the two operations together: - -repository.create_branch(branch_name).switch(branch_name) - - - -**Args:** - - - `branch_name`: name of the branch to be created - - `base`: branch or tag to be branched from - - - -**Raises:** - - - `RepositoryClientError`: if an error occur when creating a new branch - - - -**Returns:** - Repository client object. - ---- - - - -### function `create_pull_request` - -```python -create_pull_request(base: str) → PullRequest -``` - -Create pull request for changes in given repository path. - - - -**Args:** - - - `base`: tag or branch against to which the PR is opened - - - -**Raises:** - - - `InputError`: when the repository is not dirty, hence resulting on an empty pull-request - - - -**Returns:** - Pull request object - ---- - - - -### function `get_file_content_from_tag` - -```python -get_file_content_from_tag(path: str, tag_name: str) → str -``` - -Get the content of a file for a specific tag. - - - -**Args:** - - - `path`: The path to the file. - - `tag_name`: The name of the tag. - - - -**Returns:** - The content of the file for the tag. - - - -**Raises:** - - - `RepositoryTagNotFoundError`: if the tag could not be found in the repository. - - `RepositoryFileNotFoundError`: if the file could not be retrieved from GitHub, more than one file is returned or a non-file is returned - - `RepositoryClientError`: if there is a problem with communicating with GitHub - ---- - - - -### function `get_pull_request` - -```python -get_pull_request(branch_name: str) → PullRequest | None -``` - -Return open pull request matching the provided branch name. - - - -**Args:** - - - `branch_name`: branch name to select open pull requests. - - - -**Raises:** - - - `RepositoryClientError`: if more than one PR is open with the given branch name - - - -**Returns:** - PullRequest object. If no PR is found, None is returned. - ---- - - - -### function `get_summary` - -```python -get_summary(directory: str | Path | None) → DiffSummary -``` - -Return a summary of the differences against the most recent commit. - - - -**Args:** - - - `directory`: constraint committed changes to a particular folder only. If None, all the folders are committed. Default is the documentation folder. - - - -**Returns:** - DiffSummary object representing the summary of the differences. - ---- - - - -### function `is_commit_in_branch` - -```python -is_commit_in_branch(commit_sha: str, branch: str | None = None) → bool -``` - -Check if commit exists in a given branch. - - - -**Args:** - - - `commit_sha`: SHA of the commit to be searched for - - `branch`: name of the branch against which the check is done. When None, the current branch is used. - - - -**Raises:** - - - `RepositoryClientError`: when the commit is not found in the repository - - - -**Returns:** - boolean representing whether the commit exists in the branch - ---- - - - -### function `is_dirty` - -```python -is_dirty(branch_name: str | None = None) → bool -``` - -Check if repository path has any changes including new files. - - - -**Args:** - - - `branch_name`: name of the branch to be checked against dirtiness - - - -**Returns:** - True if any changes have occurred. - ---- - - - -### function `is_same_commit` - -```python -is_same_commit(tag: str, commit: str) → bool -``` - -Return whether tag and commit coincides. - - - -**Args:** - - - `tag`: name of the tag - - `commit`: sha of the commit - - - -**Returns:** - True if the two pointers coincides, False otherwise. - ---- - - - -### function `pull` - -```python -pull(branch_name: str | None = None) → None -``` - -Pull content from remote for the provided branch. - - - -**Args:** - - - `branch_name`: branch to be pulled from the remote - ---- - - - -### function `switch` - -```python -switch(branch_name: str) → Client -``` - -Switch branch for the repository. - - - -**Args:** - - - `branch_name`: name of the branch to switch to. - - - -**Returns:** - Repository object with the branch switched. - ---- - - - -### function `tag_commit` - -```python -tag_commit(tag_name: str, commit_sha: str) → None -``` - -Tag a commit, if the tag already exists, it is deleted first. - - - -**Args:** - - - `tag_name`: The name of the tag. - - `commit_sha`: The SHA of the commit to tag. - - - -**Raises:** - - - `RepositoryClientError`: if there is a problem with communicating with GitHub - ---- - - - -### function `tag_exists` - -```python -tag_exists(tag_name: str) → str | None -``` - -Check if a given tag exists. - - - -**Args:** - - - `tag_name`: name of the tag to be checked for existence - - - -**Returns:** - hash of the commit the tag refers to. - ---- - - - -### function `update_branch` - -```python -update_branch( - commit_msg: str, - directory: str | Path | None, - push: bool = True, - force: bool = False -) → Client -``` - -Update branch with a new commit. - - - -**Args:** - - - `commit_msg`: commit message to be committed to the branch - - `push`: push new changes to remote branches - - `force`: when pushing to remove, use force flag - - `directory`: constraint committed changes to a particular folder only. If None, all the folders are committed. Default is the documentation folder. - - - -**Raises:** - - - `RepositoryClientError`: if any error are encountered in the update process - - - -**Returns:** - Repository client with the updated branch - ---- - - - -### function `update_pull_request` - -```python -update_pull_request(branch: str) → None -``` - -Update and push changes to the given branch. - - - -**Args:** - - - `branch`: name of the branch to be updated - ---- - - - -### function `with_branch` - -```python -with_branch(branch_name: str) → Iterator['Client'] -``` - -Return a context for operating within the given branch. - -At the end of the 'with' block, the branch is switched back to what it was initially. - - - -**Args:** - - - `branch_name`: name of the branch - - - -**Yields:** - Context to operate on the provided branch - - ---- - -## class `DiffSummary` -Class representing the summary of the dirty status of a repository. - -Attrs: is_dirty: boolean indicated whether there is any delta new: list of files added in the delta removed: list of files removed in the delta modified: list of files modified in the delta - - - - ---- - - - -### classmethod `from_raw_diff` - -```python -from_raw_diff(diffs: Sequence[Diff]) → DiffSummary -``` - -Return a DiffSummary class from a sequence of git.Diff objects. - - - -**Args:** - - - `diffs`: list of git.Diff objects representing the delta between two snapshots. - - - -**Returns:** - DiffSummary class - - diff --git a/src-docs/sort.py.md b/src-docs/sort.py.md deleted file mode 100644 index 9d8530fc..00000000 --- a/src-docs/sort.py.md +++ /dev/null @@ -1,40 +0,0 @@ - - - - -# module `sort.py` -Sort items for publishing. - - ---- - - - -## function `using_contents_index` - -```python -using_contents_index( - path_infos: Iterable[PathInfo], - index_contents: Iterable[IndexContentsListItem], - docs_path: Path -) → Iterator[PathInfo | IndexContentsListItem] -``` - -Sort PathInfos based on the contents index and alphabetical rank. - -Also updates the navlink title for any items matched to the contents index. - - - -**Args:** - - - `path_infos`: Information about the local documentation files. - - `index_contents`: The content index items used to apply sorting. - - `docs_path`: The directory the documentation files are contained within. - - - -**Yields:** - PathInfo sorted based on their location on the contents index and then by alphabetical rank. - - diff --git a/src-docs/types_.py.md b/src-docs/types_.py.md deleted file mode 100644 index 51d1051e..00000000 --- a/src-docs/types_.py.md +++ /dev/null @@ -1,480 +0,0 @@ - - - - -# module `types_.py` -Types for uploading docs to charmhub. - - - ---- - -## class `ActionReport` -Post execution report for an action. - -Attrs: table_row: The navigation table entry, None for delete or index actions. location: The URL that the action operated on, None for groups or if a create action was skipped, if running in reconcile mode. Path to migrated file, if running in migration mode. None on action failure. result: The action execution result. reason: The reason, None for success reports. - - - - - ---- - -## class `ActionResult` -Result of taking an action. - -Attrs: SUCCESS: The action succeeded. SKIP: The action was skipped. FAIL: The action failed. - - - - - ---- - -## class `ContentChange` -Represents a change to the content. - -Attrs: base: The content which is the base for comparison. server: The content on the server. local: The content on the local disk. - - - - - ---- - -## class `CreateExternalRefAction` -Represents a external reference to be created. - -Attrs: navlink_value: The external reference. - - - - - ---- - -## class `CreateGroupAction` -Represents a group to be created. - - - - - ---- - -## class `CreateIndexAction` -Represents an index page to be created. - -Attrs: title: The title of the index page. content: The content including the navigation table. - - - - - ---- - -## class `CreatePageAction` -Represents a page to be created. - -Attrs: content: The documentation content. - - - - - ---- - -## class `DeleteExternalRefAction` -Represents an external reference to be deleted. - - - - - ---- - -## class `DeleteGroupAction` -Represents a group to be deleted. - - - - - ---- - -## class `DeletePageAction` -Represents a page to be deleted. - -Attrs: content: The documentation content. - - - - - ---- - -## class `DocumentMeta` -Represents a document to be migrated from the index table. - -Attrs: link: Link to content to read from. table_row: Document row that is the source of document file. - - - - - ---- - -## class `GitkeepMeta` -Represents an empty directory from the index table. - -Attrs: table_row: Empty group row that is the source of .gitkeep file. - - - - - ---- - -## class `Index` -Information about the local and server index page. - -Attrs: server: The index page on the server. local: The local index file contents. name: The name of the charm. - - - - - ---- - -## class `IndexContentChange` -Represents a change to the content of the index. - -Attrs: old: The previous content. new: The new content. - - - - - ---- - -## class `IndexContentsListItem` -Represents an item in the contents table. - -Attrs: hierarchy: The number of parent items to the root of the list reference_title: The name of the reference reference_value: The link to the referenced item rank: The number of preceding elements in the list at any hierarchy hidden: Whether the item should be displayed on the navigation table table_path: The path for the item on the table. is_external: Whether the item is an external reference. - - ---- - -#### property is_external - -Whether the row is an external reference. - - - -**Returns:** - Whether the item in the table is an external item. - ---- - -#### property table_path - -The table path for the item. - -In the case of a HTTP reference, changes http://canonical.com/1 to http,canonical,com,1 removing the HTTP protocol characters so that the path conforms to the path in the non-HTTP case. Any remaining characters not allowed in the path are also removed. For a non-HTTP case, removes the file suffix and splits on / to built the path. - - - -**Returns:** - The table path for the item. - - - - ---- - -## class `IndexDocumentMeta` -Represents an index file document. - -Attrs: content: Contents to write to index file. - - - - - ---- - -## class `IndexFile` -Information about a documentation page. - -Attrs: title: The title for the index. content: The local content of the index. - - - - - ---- - -## class `Metadata` -Information within metadata file. Refer to: https://juju.is/docs/sdk/metadata-yaml. - -Only name and docs are the fields of interest for the scope of this module. - -Attrs: name: Name of the charm. docs: A link to a documentation cover page on Discourse. - - - - - ---- - -## class `MigrateOutputs` -Output provided by the reconcile workflow. - -Attrs: action: Action taken on the PR pull_request_url: url of the pull-request when relevant - - - - - ---- - -## class `MigrationFileMeta` -Metadata about a document to be migrated. - -Attrs: path: The full document path to be written to. - - - - - ---- - -## class `Navlink` -Represents navlink of a table row of the navigation table. - -Attrs: title: The title of the documentation page. link: The relative URL to the documentation page or None if there is no link. hidden: Whether the item should be displayed on the navigation table. - - - - - ---- - -## class `NavlinkChange` -Represents a change to the navlink. - -Attrs: old: The previous navlink. new: The new navlink. - - - - - ---- - -## class `NoopExternalRefAction` -Represents an external reference with no required changes. - - - - - ---- - -## class `NoopGroupAction` -Represents a group with no required changes. - - - - - ---- - -## class `NoopIndexAction` -Represents an index page with no required changes. - -Attrs: content: The content including the navigation table. url: The URL to the index page. - - - - - ---- - -## class `NoopPageAction` -Represents a page with no required changes. - -Attrs: content: The documentation content of the page. - - - - - ---- - -## class `Page` -Information about a documentation page. - -Attrs: url: The link to the page. content: The documentation text of the page. - - - - - ---- - -## class `PathInfo` -Represents a file or directory in the docs directory. - -Attrs: local_path: The path to the file on the local disk. level: The number of parent directories to the docs folder including the docs folder. table_path: The computed table path based on the disk path relative to the docs folder. navlink_title: The title of the navlink. alphabetical_rank: The rank of the path info based on alphabetically sorting all relevant path infos. navlink_hidden: Whether the item should be displayed on the navigation table - - - - - ---- - -## class `PullRequestAction` -Result of taking an action. - -Attrs: OPENED: A new PR has been opened. CLOSED: An existing PR has been closed. UPDATED: An existing PR has been updated. - - - - - ---- - -## class `ReconcileOutputs` -Output provided by the reconcile workflow. - -Attrs: index_url: url with the root documentation topic on Discourse topics: List of urls with actions documentation_tag: commit sha to which the tag was created - - - - - ---- - -## class `TableRow` -Represents one parsed row of the navigation table. - -Attrs: level: The number of parents, is 1 if there is no parent. path: The a unique string identifying the row. navlink: The title and relative URL to the documentation page. is_group: Whether the row is the parent of zero or more other rows. - - ---- - -#### property is_group - -Whether the row is a group of pages. - - - ---- - - - -### function `is_external` - -```python -is_external(server_hostname: str) → bool -``` - -Whether the row is an external reference. - - - -**Args:** - - - `server_hostname`: The hostname of the discourse server. - - - -**Returns:** - Whether the item in the table is an external item. - ---- - - - -### function `to_markdown` - -```python -to_markdown(server_hostname: str) → str -``` - -Convert to a line in the navigation table. - - - -**Args:** - - - `server_hostname`: The hostname of the discourse server. - - - -**Returns:** - The line in the navigation table. - - ---- - -## class `UpdateExternalRefAction` -Represents an external reference to be updated. - - - - - ---- - -## class `UpdateGroupAction` -Represents a group to be updated. - - - - - ---- - -## class `UpdateIndexAction` -Represents an index page to be updated. - -Attrs: content_change: The change to the content including the navigation table. url: The URL to the index page. - - - - - ---- - -## class `UpdatePageAction` -Represents a page to be updated. - -Attrs: content_change: The change to the documentation content. - - - - - ---- - -## class `UserInputs` -Configurable user input values used to run discourse-gatekeeper. - -Attrs: discourse: The configuration for interacting with discourse. dry_run: If enabled, only log the action that would be taken. Has no effect in migration mode. delete_pages: Whether to delete pages that are no longer needed. Has no effect in migration mode. github_access_token: A Personal Access Token(PAT) or access token with repository access. Required in migration mode. commit_sha: The SHA of the commit the action is running on. base_branch: The main branch against which the syncs act on. charm_dir: Directory the charm is located in. - - - - - ---- - -## class `UserInputsDiscourse` -Configurable user input values used to run discourse-gatekeeper. - -Attrs: hostname: The base path to the discourse server. category_id: The category identifier to use on discourse for all topics. api_username: The discourse API username to use for interactions with the server. api_key: The discourse API key to use for interactions with the server. - - - - - diff --git a/src/__init__.py b/src/gatekeeper/__init__.py similarity index 94% rename from src/__init__.py rename to src/gatekeeper/__init__.py index 30405066..95360a80 100644 --- a/src/__init__.py +++ b/src/gatekeeper/__init__.py @@ -6,17 +6,17 @@ from collections.abc import Iterable, Iterator from itertools import tee -from src import action, check, docs_directory -from src import index as index_module -from src import navigation_table, reconcile -from src import sort as sort_module -from src.action import DRY_RUN_NAVLINK_LINK, FAIL_NAVLINK_LINK -from src.clients import Clients -from src.constants import DOCUMENTATION_TAG -from src.download import recreate_docs -from src.exceptions import InputError, TaggingNotAllowedError -from src.repository import DEFAULT_BRANCH_NAME -from src.types_ import ( +from gatekeeper import action, check, docs_directory +from gatekeeper import index as index_module +from gatekeeper import navigation_table, reconcile +from gatekeeper import sort as sort_module +from gatekeeper.action import DRY_RUN_NAVLINK_LINK, FAIL_NAVLINK_LINK +from gatekeeper.clients import Clients +from gatekeeper.constants import DOCUMENTATION_TAG +from gatekeeper.download import recreate_docs +from gatekeeper.exceptions import InputError, TaggingNotAllowedError +from gatekeeper.repository import DEFAULT_BRANCH_NAME +from gatekeeper.types_ import ( ActionResult, AnyAction, Index, diff --git a/src/action.py b/src/gatekeeper/action.py similarity index 99% rename from src/action.py rename to src/gatekeeper/action.py index 1702da92..85e1d403 100644 --- a/src/action.py +++ b/src/gatekeeper/action.py @@ -7,8 +7,8 @@ import typing from enum import Enum -from src import content, exceptions, reconcile, types_ -from src.discourse import Discourse +from gatekeeper import content, exceptions, reconcile, types_ +from gatekeeper.discourse import Discourse DRY_RUN_NAVLINK_LINK = "" DRY_RUN_REASON = "dry run" diff --git a/src/check.py b/src/gatekeeper/check.py similarity index 98% rename from src/check.py rename to src/gatekeeper/check.py index f5e1a208..07adcd12 100644 --- a/src/check.py +++ b/src/gatekeeper/check.py @@ -10,9 +10,9 @@ import requests -from src import content -from src.constants import DOCUMENTATION_TAG -from src.types_ import ( +from gatekeeper import content +from gatekeeper.constants import DOCUMENTATION_TAG +from gatekeeper.types_ import ( AnyAction, IndexContentsListItem, UpdateAction, diff --git a/src/clients.py b/src/gatekeeper/clients.py similarity index 84% rename from src/clients.py rename to src/gatekeeper/clients.py index c9d732a3..5e843c60 100644 --- a/src/clients.py +++ b/src/gatekeeper/clients.py @@ -6,10 +6,10 @@ import typing from pathlib import Path -from src.discourse import Discourse, create_discourse -from src.repository import Client as RepositoryClient -from src.repository import create_repository_client -from src.types_ import UserInputs +from gatekeeper.discourse import Discourse, create_discourse +from gatekeeper.repository import Client as RepositoryClient +from gatekeeper.repository import create_repository_client +from gatekeeper.types_ import UserInputs class Clients(typing.NamedTuple): diff --git a/src/commit.py b/src/gatekeeper/commit.py similarity index 100% rename from src/commit.py rename to src/gatekeeper/commit.py diff --git a/src/constants.py b/src/gatekeeper/constants.py similarity index 100% rename from src/constants.py rename to src/gatekeeper/constants.py diff --git a/src/content.py b/src/gatekeeper/content.py similarity index 98% rename from src/content.py rename to src/gatekeeper/content.py index 9ffea9a5..e50474a4 100644 --- a/src/content.py +++ b/src/gatekeeper/content.py @@ -10,7 +10,7 @@ from git.exc import GitCommandError from git.repo import Repo -from src.exceptions import ContentError +from gatekeeper.exceptions import ContentError _BASE_BRANCH = "base" _THEIR_BRANCH = "theirs" diff --git a/src/discourse.py b/src/gatekeeper/discourse.py similarity index 99% rename from src/discourse.py rename to src/gatekeeper/discourse.py index 8af01af4..406e4102 100644 --- a/src/discourse.py +++ b/src/gatekeeper/discourse.py @@ -12,7 +12,7 @@ from requests.adapters import HTTPAdapter from urllib3 import Retry -from src.exceptions import DiscourseError, InputError +from gatekeeper.exceptions import DiscourseError, InputError _URL_PATH_PREFIX = "/t/" _POST_SPLIT_LINE = "\n\n-------------------------\n\n" diff --git a/src/docs_directory.py b/src/gatekeeper/docs_directory.py similarity index 98% rename from src/docs_directory.py rename to src/gatekeeper/docs_directory.py index 5e9230be..7c2df454 100644 --- a/src/docs_directory.py +++ b/src/gatekeeper/docs_directory.py @@ -8,8 +8,8 @@ from itertools import count from pathlib import Path -from src import types_ -from src.constants import DOC_FILE_EXTENSION +from gatekeeper import types_ +from gatekeeper.constants import DOC_FILE_EXTENSION def _get_directories_files(docs_path: Path) -> list[Path]: diff --git a/src/download.py b/src/gatekeeper/download.py similarity index 85% rename from src/download.py rename to src/gatekeeper/download.py index f16d9809..8b31ce9c 100644 --- a/src/download.py +++ b/src/gatekeeper/download.py @@ -5,11 +5,11 @@ import shutil -from src.clients import Clients -from src.index import contents_from_page -from src.index import get as get_index -from src.migration import run as migrate_contents -from src.navigation_table import from_page as navigation_table_from_page +from gatekeeper.clients import Clients +from gatekeeper.index import contents_from_page +from gatekeeper.index import get as get_index +from gatekeeper.migration import run as migrate_contents +from gatekeeper.navigation_table import from_page as navigation_table_from_page def _download_from_discourse(clients: Clients) -> None: diff --git a/src/exceptions.py b/src/gatekeeper/exceptions.py similarity index 100% rename from src/exceptions.py rename to src/gatekeeper/exceptions.py diff --git a/src/index.py b/src/gatekeeper/index.py similarity index 97% rename from src/index.py rename to src/gatekeeper/index.py index 46a3eedf..5dbf1485 100644 --- a/src/index.py +++ b/src/gatekeeper/index.py @@ -10,10 +10,14 @@ from enum import Enum, auto from pathlib import Path -from src.constants import DOC_FILE_EXTENSION, DOCUMENTATION_INDEX_FILENAME, NAVIGATION_HEADING -from src.discourse import Discourse -from src.exceptions import DiscourseError, InputError, ServerError -from src.types_ import Index, IndexContentsListItem, IndexFile, Metadata, Page +from gatekeeper.constants import ( + DOC_FILE_EXTENSION, + DOCUMENTATION_INDEX_FILENAME, + NAVIGATION_HEADING, +) +from gatekeeper.discourse import Discourse +from gatekeeper.exceptions import DiscourseError, InputError, ServerError +from gatekeeper.types_ import Index, IndexContentsListItem, IndexFile, Metadata, Page CONTENTS_HEADER = "# contents" CONTENTS_END_LINE_PREFIX = "#" diff --git a/src/metadata.py b/src/gatekeeper/metadata.py similarity index 98% rename from src/metadata.py rename to src/gatekeeper/metadata.py index ec4de83d..a9817eb1 100644 --- a/src/metadata.py +++ b/src/gatekeeper/metadata.py @@ -7,8 +7,8 @@ import yaml -from src import types_ -from src.exceptions import InputError +from gatekeeper import types_ +from gatekeeper.exceptions import InputError CHARMCRAFT_FILENAME = "charmcraft.yaml" CHARMCRAFT_NAME_KEY = "name" diff --git a/src/migration.py b/src/gatekeeper/migration.py similarity index 99% rename from src/migration.py rename to src/gatekeeper/migration.py index 185d8629..aa33c9b1 100644 --- a/src/migration.py +++ b/src/gatekeeper/migration.py @@ -8,8 +8,8 @@ import typing from pathlib import Path -from src import exceptions, types_ -from src.discourse import Discourse +from gatekeeper import exceptions, types_ +from gatekeeper.discourse import Discourse EMPTY_DIR_REASON = "" GITKEEP_FILENAME = ".gitkeep" diff --git a/src/navigation_table.py b/src/gatekeeper/navigation_table.py similarity index 98% rename from src/navigation_table.py rename to src/gatekeeper/navigation_table.py index 108254c4..797d5744 100644 --- a/src/navigation_table.py +++ b/src/gatekeeper/navigation_table.py @@ -7,9 +7,9 @@ import string import typing -from src import constants, types_ -from src.discourse import Discourse -from src.exceptions import ( +from gatekeeper import constants, types_ +from gatekeeper.discourse import Discourse +from gatekeeper.exceptions import ( DiscourseError, NavigationTableParseError, PagePermissionError, diff --git a/src/reconcile.py b/src/gatekeeper/reconcile.py similarity index 98% rename from src/reconcile.py rename to src/gatekeeper/reconcile.py index 20e62258..e3469f7c 100644 --- a/src/reconcile.py +++ b/src/gatekeeper/reconcile.py @@ -7,12 +7,12 @@ import typing from pathlib import Path -from src import exceptions -from src import index as index_module -from src import types_ -from src.clients import Clients -from src.constants import DOCUMENTATION_TAG, NAVIGATION_TABLE_START -from src.discourse import Discourse +from gatekeeper import exceptions +from gatekeeper import index as index_module +from gatekeeper import types_ +from gatekeeper.clients import Clients +from gatekeeper.constants import DOCUMENTATION_TAG, NAVIGATION_TABLE_START +from gatekeeper.discourse import Discourse def _local_only(item_info: types_.PathInfo | types_.IndexContentsListItem) -> types_.CreateAction: diff --git a/src/repository.py b/src/gatekeeper/repository.py similarity index 99% rename from src/repository.py rename to src/gatekeeper/repository.py index 96043b42..c97452ac 100644 --- a/src/repository.py +++ b/src/gatekeeper/repository.py @@ -23,17 +23,17 @@ from github.PullRequest import PullRequest from github.Repository import Repository -from src import commit as commit_module -from src.constants import DOCUMENTATION_FOLDER_NAME -from src.docs_directory import has_docs_directory -from src.exceptions import ( +from gatekeeper import commit as commit_module +from gatekeeper.constants import DOCUMENTATION_FOLDER_NAME +from gatekeeper.docs_directory import has_docs_directory +from gatekeeper.exceptions import ( InputError, RepositoryClientError, RepositoryFileNotFoundError, RepositoryTagNotFoundError, ) -from src.metadata import get as get_metadata -from src.types_ import Metadata +from gatekeeper.metadata import get as get_metadata +from gatekeeper.types_ import Metadata GITHUB_HOSTNAME = "github.com" ORIGIN_NAME = "origin" diff --git a/src/sort.py b/src/gatekeeper/sort.py similarity index 99% rename from src/sort.py rename to src/gatekeeper/sort.py index 56e33e86..4565491f 100644 --- a/src/sort.py +++ b/src/gatekeeper/sort.py @@ -9,7 +9,7 @@ from more_itertools import peekable, side_effect -from src import index, types_ +from gatekeeper import index, types_ class _SortData(typing.NamedTuple): diff --git a/src/types_.py b/src/gatekeeper/types_.py similarity index 99% rename from src/types_.py rename to src/gatekeeper/types_.py index d07decaa..8363faa4 100644 --- a/src/types_.py +++ b/src/gatekeeper/types_.py @@ -10,7 +10,7 @@ from pathlib import Path from urllib.parse import urlparse -from src import constants +from gatekeeper import constants Content = str Url = str diff --git a/tests/conftest.py b/tests/conftest.py index c9067077..10e02a28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,9 +13,9 @@ from github.Repository import Repository from github.Requester import Requester -import src -from src import repository -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME, DOCUMENTATION_TAG +import gatekeeper +from gatekeeper import repository +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME, DOCUMENTATION_TAG # This is a fake branch to be used in the remote repository to prevent conflicts when # pushing main. Another option would be to use remote bare repository, but this would @@ -169,4 +169,6 @@ def mock_create_repository_client(**_kwargs): """Mock create_repository_client patch function.""" # noqa: DCO020 return repository_client # noqa: DCO030 - monkeypatch.setattr(src.clients, "create_repository_client", mock_create_repository_client) + monkeypatch.setattr( + gatekeeper.clients, "create_repository_client", mock_create_repository_client + ) diff --git a/tests/factories.py b/tests/factories.py index 63a902b6..6f4fc9f3 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -11,8 +11,8 @@ import factory -from src import index, types_ -from src.constants import DEFAULT_BRANCH +from gatekeeper import index, types_ +from gatekeeper.constants import DEFAULT_BRANCH from . import types diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 72e19e32..3058f967 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -20,7 +20,7 @@ from juju.unit import Unit from pytest_operator.plugin import OpsTest -from src.discourse import Discourse +from gatekeeper.discourse import Discourse from . import types diff --git a/tests/integration/test___init__run_conflict.py b/tests/integration/test___init__run_conflict.py index d979a95a..6985af51 100644 --- a/tests/integration/test___init__run_conflict.py +++ b/tests/integration/test___init__run_conflict.py @@ -19,9 +19,9 @@ import pytest from github.ContentFile import ContentFile -from src import Clients, constants, exceptions, metadata, repository, run_reconcile -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG -from src.discourse import Discourse +from gatekeeper import Clients, constants, exceptions, metadata, repository, run_reconcile +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG +from gatekeeper.discourse import Discourse from .. import factories from ..unit.helpers import assert_substrings_in_string, create_metadata_yaml diff --git a/tests/integration/test___init__run_migrate.py b/tests/integration/test___init__run_migrate.py index ed31b5c9..09ebadb3 100644 --- a/tests/integration/test___init__run_migrate.py +++ b/tests/integration/test___init__run_migrate.py @@ -14,12 +14,12 @@ from git.repo import Repo from github.PullRequest import PullRequest -from src import Clients, constants, metadata, migration, run_migrate -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG -from src.discourse import Discourse -from src.repository import DEFAULT_BRANCH_NAME -from src.repository import Client as RepositoryClient -from src.types_ import PullRequestAction +from gatekeeper import Clients, constants, metadata, migration, run_migrate +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG +from gatekeeper.discourse import Discourse +from gatekeeper.repository import DEFAULT_BRANCH_NAME +from gatekeeper.repository import Client as RepositoryClient +from gatekeeper.types_ import PullRequestAction from .. import factories from ..conftest import BASE_REMOTE_BRANCH diff --git a/tests/integration/test___init__run_reconcile.py b/tests/integration/test___init__run_reconcile.py index 4d33339d..a37c3798 100644 --- a/tests/integration/test___init__run_reconcile.py +++ b/tests/integration/test___init__run_reconcile.py @@ -17,10 +17,10 @@ import pytest from github.ContentFile import ContentFile -from src import Clients, constants, exceptions, metadata, run_reconcile -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG -from src.discourse import Discourse -from src.repository import Client, Repo +from gatekeeper import Clients, constants, exceptions, metadata, run_reconcile +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_TAG +from gatekeeper.discourse import Discourse +from gatekeeper.repository import Client, Repo from .. import factories from ..unit.helpers import assert_substrings_in_string, create_metadata_yaml diff --git a/tests/integration/test_discourse.py b/tests/integration/test_discourse.py index eb2f5140..08a82ce9 100644 --- a/tests/integration/test_discourse.py +++ b/tests/integration/test_discourse.py @@ -9,8 +9,8 @@ import pydiscourse import pytest -from src.discourse import Discourse -from src.exceptions import DiscourseError +from gatekeeper.discourse import Discourse +from gatekeeper.exceptions import DiscourseError from . import types diff --git a/tests/unit/action/test_other_actions.py b/tests/unit/action/test_other_actions.py index e44235d1..1f7ab3ba 100644 --- a/tests/unit/action/test_other_actions.py +++ b/tests/unit/action/test_other_actions.py @@ -12,8 +12,8 @@ import pytest -from src import action, discourse, exceptions -from src import types_ as src_types +from gatekeeper import action, discourse, exceptions +from gatekeeper import types_ as src_types from ... import factories from ..helpers import assert_substrings_in_string diff --git a/tests/unit/action/test_update_action.py b/tests/unit/action/test_update_action.py index cbb54b57..995fba89 100644 --- a/tests/unit/action/test_update_action.py +++ b/tests/unit/action/test_update_action.py @@ -12,8 +12,8 @@ import pytest -from src import action, discourse, exceptions -from src import types_ as src_types +from gatekeeper import action, discourse, exceptions +from gatekeeper import types_ as src_types from ... import factories from ..helpers import assert_substrings_in_string diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 359db76f..1d24def7 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -9,8 +9,8 @@ import pytest import requests -from src import Clients, constants, repository -from src.discourse import Discourse +from gatekeeper import Clients, constants, repository +from gatekeeper.discourse import Discourse from . import helpers diff --git a/tests/unit/helpers.py b/tests/unit/helpers.py index 61007d3f..8f6e2a2d 100644 --- a/tests/unit/helpers.py +++ b/tests/unit/helpers.py @@ -6,8 +6,8 @@ import typing from pathlib import Path -from src import metadata -from src.discourse import _URL_PATH_PREFIX +from gatekeeper import metadata +from gatekeeper.discourse import _URL_PATH_PREFIX def create_metadata_yaml(content: str, path: Path) -> None: diff --git a/tests/unit/test___init__.py b/tests/unit/test___init__.py index 5b559d67..70222020 100644 --- a/tests/unit/test___init__.py +++ b/tests/unit/test___init__.py @@ -10,7 +10,7 @@ from git.repo import Repo from github.PullRequest import PullRequest -from src import ( # GETTING_STARTED, +from gatekeeper import ( # GETTING_STARTED, DOCUMENTATION_TAG, Clients, constants, @@ -21,11 +21,11 @@ run_reconcile, types_, ) -from src.clients import get_clients -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME -from src.metadata import METADATA_DOCS_KEY, METADATA_NAME_KEY -from src.repository import DEFAULT_BRANCH_NAME -from src.repository import Client as RepositoryClient +from gatekeeper.clients import get_clients +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME +from gatekeeper.metadata import METADATA_DOCS_KEY, METADATA_NAME_KEY +from gatekeeper.repository import DEFAULT_BRANCH_NAME +from gatekeeper.repository import Client as RepositoryClient from .. import factories from ..conftest import BASE_REMOTE_BRANCH @@ -61,7 +61,7 @@ def test_setup_clients(get_repo_mock, git_repo_with_remote, charm_dir): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_empty_local_server(mocked_clients): @@ -91,7 +91,7 @@ def test__run_reconcile_empty_local_server(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_empty_local_server_from_non_base_branch(mocked_clients): @@ -124,7 +124,7 @@ def test__run_reconcile_empty_local_server_from_non_base_branch(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_local_empty_server(mocked_clients): @@ -171,7 +171,7 @@ def test__run_reconcile_local_empty_server(mocked_clients): } -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") @pytest.mark.parametrize( "branch_name", [pytest.param(DEFAULT_BRANCH), pytest.param("other-main")], @@ -282,7 +282,7 @@ def patch(path, tag_name) -> str: @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_local_contents_index(mocked_clients): @@ -340,7 +340,7 @@ def test__run_reconcile_local_contents_index(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_hidden_item(mocked_clients): @@ -390,7 +390,7 @@ def test__run_reconcile_hidden_item(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_invalid_external_item(mocked_clients): @@ -424,7 +424,7 @@ def test__run_reconcile_invalid_external_item(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_external_item(mocked_clients): @@ -470,7 +470,7 @@ def test__run_reconcile_external_item(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_local_empty_server_dry_run(mocked_clients): @@ -492,7 +492,7 @@ def test__run_reconcile_local_empty_server_dry_run(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_local_empty_server_dry_run_no_tag(mocked_clients, upstream_git_repo): @@ -518,7 +518,7 @@ def test__run_reconcile_local_empty_server_dry_run_no_tag(mocked_clients, upstre @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_reconcile_local_empty_server_error(mocked_clients): @@ -550,10 +550,10 @@ def test__run_reconcile_local_empty_server_error(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="index-url"), ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__run_reconcile_local_server_conflict(mock_tag, mocked_clients): """ arrange: given metadata with name and docs and docs folder with a file and mocked discourse @@ -591,7 +591,7 @@ def test__run_reconcile_local_server_conflict(mock_tag, mocked_clients): mocked_clients.discourse.retrieve_topic.assert_any_call(url=page_url) -@mock.patch("src.repository.Client.metadata", types_.Metadata(name="name 1", docs=None)) +@mock.patch("gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None)) def test__run_reconcile_no_docs(caplog, mocked_clients): """ arrange: given metadata with name and no docs and no docs folder and mocked discourse @@ -611,7 +611,7 @@ def test__run_reconcile_no_docs(caplog, mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) def test__run_reconcile_on_tag_commit(caplog, mocked_clients): @@ -645,7 +645,7 @@ def test__run_reconcile_on_tag_commit(caplog, mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) def test__run_migrate_server_error_index(repository_client: RepositoryClient): @@ -669,7 +669,7 @@ def test__run_migrate_server_error_index(repository_client: RepositoryClient): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) def test__run_migrate_server_error_topic(mocked_clients): @@ -703,7 +703,7 @@ def test__run_migrate_server_error_topic(mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs=None), ) def test__run_migrate_no_docs_information(caplog, mocked_clients): @@ -727,7 +727,7 @@ def test__run_migrate_no_docs_information(caplog, mocked_clients): @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) def test__run_migrate( @@ -781,10 +781,10 @@ def test__run_migrate( @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) -@mock.patch("src.repository.Client.get_pull_request") +@mock.patch("gatekeeper.repository.Client.get_pull_request") def test__run_migrate_with_pull_request( mock_get_pull_request, mocked_clients, @@ -844,10 +844,10 @@ def test__run_migrate_with_pull_request( @mock.patch( - "src.repository.Client.metadata", + "gatekeeper.repository.Client.metadata", types_.Metadata(name="name 1", docs="http://discourse/t/docs"), ) -@mock.patch("src.repository.Client.get_pull_request") +@mock.patch("gatekeeper.repository.Client.get_pull_request") def test__run_migrate_with_pull_request_no_modification( mock_get_pull_request, mocked_clients, @@ -1118,7 +1118,7 @@ def test_run_migrate_same_content_local_and_server(mock_edit_pull_request, caplo assert not edit_call_args -@mock.patch("src.repository.Client.get_pull_request") +@mock.patch("gatekeeper.repository.Client.get_pull_request") @mock.patch("github.PullRequest.PullRequest") def test_run_migrate_same_content_local_and_server_open_pr( mocked_get_pull_request, mock_edit_pull_request, caplog, mocked_clients, mock_pull_request diff --git a/tests/unit/test_check.py b/tests/unit/test_check.py index 06c0efbc..b9461801 100644 --- a/tests/unit/test_check.py +++ b/tests/unit/test_check.py @@ -8,7 +8,7 @@ import pytest -from src import check, types_ +from gatekeeper import check, types_ from .. import factories from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_commit.py b/tests/unit/test_commit.py index 5e27d2fb..4c1c8c01 100644 --- a/tests/unit/test_commit.py +++ b/tests/unit/test_commit.py @@ -9,9 +9,9 @@ import typing from pathlib import Path -from src import commit -from src.constants import DEFAULT_BRANCH -from src.repository import Client +from gatekeeper import commit +from gatekeeper.constants import DEFAULT_BRANCH +from gatekeeper.repository import Client def test_parse_git_show_empty(): diff --git a/tests/unit/test_content.py b/tests/unit/test_content.py index b70a1788..0d471f2c 100644 --- a/tests/unit/test_content.py +++ b/tests/unit/test_content.py @@ -5,7 +5,7 @@ import pytest -from src import content, exceptions +from gatekeeper import content, exceptions from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_discourse.py b/tests/unit/test_discourse.py index 50d0d22b..0842546a 100644 --- a/tests/unit/test_discourse.py +++ b/tests/unit/test_discourse.py @@ -14,8 +14,8 @@ import pytest import requests -from src.discourse import _URL_PATH_PREFIX, Discourse, create_discourse -from src.exceptions import DiscourseError, InputError +from gatekeeper.discourse import _URL_PATH_PREFIX, Discourse, create_discourse +from gatekeeper.exceptions import DiscourseError, InputError from . import helpers diff --git a/tests/unit/test_docs_directory.py b/tests/unit/test_docs_directory.py index c7c281b7..fadb78c5 100644 --- a/tests/unit/test_docs_directory.py +++ b/tests/unit/test_docs_directory.py @@ -10,7 +10,7 @@ import pytest -from src import docs_directory, types_ +from gatekeeper import docs_directory, types_ from .. import factories diff --git a/tests/unit/test_download.py b/tests/unit/test_download.py index 58639149..6eb06b6f 100644 --- a/tests/unit/test_download.py +++ b/tests/unit/test_download.py @@ -5,9 +5,9 @@ import pytest -from src import DOCUMENTATION_TAG, constants -from src.download import recreate_docs -from src.metadata import METADATA_DOCS_KEY, METADATA_NAME_KEY +from gatekeeper import DOCUMENTATION_TAG, constants +from gatekeeper.download import recreate_docs +from gatekeeper.metadata import METADATA_DOCS_KEY, METADATA_NAME_KEY from .helpers import create_metadata_yaml diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index 8f370d1b..66eb568b 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -11,8 +11,8 @@ import pytest -from src import constants, discourse, index, types_ -from src.exceptions import DiscourseError, ServerError +from gatekeeper import constants, discourse, index, types_ +from gatekeeper.exceptions import DiscourseError, ServerError from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_index_contents_get.py b/tests/unit/test_index_contents_get.py index 600668ef..617293e6 100644 --- a/tests/unit/test_index_contents_get.py +++ b/tests/unit/test_index_contents_get.py @@ -8,7 +8,7 @@ import pytest -from src import index, types_ +from gatekeeper import index, types_ from .. import factories diff --git a/tests/unit/test_index_contents_hierarchy.py b/tests/unit/test_index_contents_hierarchy.py index a0c47891..6368fdde 100644 --- a/tests/unit/test_index_contents_hierarchy.py +++ b/tests/unit/test_index_contents_hierarchy.py @@ -11,7 +11,7 @@ import pytest -from src import constants, exceptions, index, types_ +from gatekeeper import constants, exceptions, index, types_ from .. import factories from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_index_contents_parse.py b/tests/unit/test_index_contents_parse.py index dea675c4..7eef8903 100644 --- a/tests/unit/test_index_contents_parse.py +++ b/tests/unit/test_index_contents_parse.py @@ -10,7 +10,7 @@ import pytest -from src import exceptions, index, types_ +from gatekeeper import exceptions, index, types_ from .. import factories from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index dcaf8041..f8dcb4c1 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -7,7 +7,7 @@ import pytest -from src import exceptions, metadata, types_ +from gatekeeper import exceptions, metadata, types_ from .helpers import assert_substrings_in_string, create_charmcraft_yaml, create_metadata_yaml diff --git a/tests/unit/test_migration/test_private.py b/tests/unit/test_migration/test_private.py index 5ad8a70a..7e719c1b 100644 --- a/tests/unit/test_migration/test_private.py +++ b/tests/unit/test_migration/test_private.py @@ -13,7 +13,7 @@ import pytest -from src import discourse, exceptions, migration, types_ +from gatekeeper import discourse, exceptions, migration, types_ from ... import factories from ..helpers import assert_substrings_in_string diff --git a/tests/unit/test_migration/test_public.py b/tests/unit/test_migration/test_public.py index 7fd85d8e..2e147768 100644 --- a/tests/unit/test_migration/test_public.py +++ b/tests/unit/test_migration/test_public.py @@ -9,7 +9,7 @@ import pytest -from src import discourse, exceptions, migration, types_ +from gatekeeper import discourse, exceptions, migration, types_ from ... import factories diff --git a/tests/unit/test_navigation_table.py b/tests/unit/test_navigation_table.py index baf050fa..6c772beb 100644 --- a/tests/unit/test_navigation_table.py +++ b/tests/unit/test_navigation_table.py @@ -8,8 +8,8 @@ import pytest -from src import exceptions, navigation_table, types_ -from src.exceptions import NavigationTableParseError +from gatekeeper import exceptions, navigation_table, types_ +from gatekeeper.exceptions import NavigationTableParseError from .. import factories from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_reconcile.py b/tests/unit/test_reconcile.py index e6e374a1..cd750681 100644 --- a/tests/unit/test_reconcile.py +++ b/tests/unit/test_reconcile.py @@ -13,7 +13,7 @@ import pytest -from src import constants, exceptions, reconcile, types_ +from gatekeeper import constants, exceptions, reconcile, types_ from .. import factories from .helpers import assert_substrings_in_string @@ -213,7 +213,7 @@ def test__local_and_server_file_same(local_content: str, server_content: str, mo mocked_clients.discourse.retrieve_topic.assert_called_once_with(url=navlink_link) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_content_change_repo_error(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with no changes and discourse client that @@ -254,7 +254,7 @@ def test__local_and_server_file_content_change_repo_error(mock_get_file, mocked_ ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_content_change_repo_tag_not_found(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with no changes and discourse client that @@ -295,7 +295,7 @@ def test__local_and_server_file_content_change_repo_tag_not_found(mock_get_file, ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_content_change_file_not_in_repo(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with no changes and discourse client that @@ -340,7 +340,7 @@ def test__local_and_server_file_content_change_file_not_in_repo(mock_get_file, m ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_content_change(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with no changes and discourse client that @@ -386,7 +386,7 @@ def test__local_and_server_file_content_change(mock_get_file, mocked_clients): ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_content_change_base_content_ws(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with no changes and discourse client that @@ -435,7 +435,7 @@ def test__local_and_server_file_content_change_base_content_ws(mock_get_file, mo ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_navlink_title_change(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with different navlink title and discourse @@ -479,7 +479,7 @@ def test__local_and_server_file_navlink_title_change(mock_get_file, mocked_clien ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_file_navlink_hidden_change(mock_get_file, mocked_clients): """ arrange: given path info with a file and table row with different navlink hidden and discourse @@ -525,7 +525,7 @@ def test__local_and_server_file_navlink_hidden_change(mock_get_file, mocked_clie ) -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_directory_same(mock_get_file, mocked_clients): """ arrange: given path info with a directory and table row with no changes @@ -590,7 +590,7 @@ def test__local_and_server_directory_navlink_title_changed(mocked_clients): mocked_clients.discourse.retrieve_topic.assert_not_called() -@mock.patch("src.repository.Client.get_file_content_from_tag") +@mock.patch("gatekeeper.repository.Client.get_file_content_from_tag") def test__local_and_server_external_ref_same(mock_get_file, mocked_clients): """ arrange: given item info with a external ref and table row with no changes diff --git a/tests/unit/test_repository.py b/tests/unit/test_repository.py index b85a8a2d..66a356b7 100644 --- a/tests/unit/test_repository.py +++ b/tests/unit/test_repository.py @@ -21,15 +21,15 @@ from github.PullRequest import PullRequest from github.Repository import Repository -from src import commit, repository -from src.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME, DOCUMENTATION_TAG -from src.exceptions import ( +from gatekeeper import commit, repository +from gatekeeper.constants import DEFAULT_BRANCH, DOCUMENTATION_FOLDER_NAME, DOCUMENTATION_TAG +from gatekeeper.exceptions import ( InputError, RepositoryClientError, RepositoryFileNotFoundError, RepositoryTagNotFoundError, ) -from src.repository import Client +from gatekeeper.repository import Client from .helpers import assert_substrings_in_string diff --git a/tests/unit/test_sort.py b/tests/unit/test_sort.py index 16898251..37810f8b 100644 --- a/tests/unit/test_sort.py +++ b/tests/unit/test_sort.py @@ -11,7 +11,7 @@ import pytest -from src import sort, types_ +from gatekeeper import sort, types_ from .. import factories diff --git a/tests/unit/test_types_.py b/tests/unit/test_types_.py index e526d1dd..9226f674 100644 --- a/tests/unit/test_types_.py +++ b/tests/unit/test_types_.py @@ -5,7 +5,7 @@ import pytest -from src import types_ +from gatekeeper import types_ from .. import factories From 2b43e9df000bd3cc88e64980f8abedd45844b25a Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 10:55:39 +0200 Subject: [PATCH 2/7] Update src-docs generation and include src-docs directory --- generate-src-docs.sh | 2 +- src-docs/__init__.py.md | 100 ++++++ src-docs/action.py.md | 60 ++++ src-docs/check.py.md | 117 +++++++ src-docs/clients.py.md | 44 +++ src-docs/commit.py.md | 67 ++++ src-docs/constants.py.md | 21 ++ src-docs/content.py.md | 91 +++++ src-docs/discourse.py.md | 300 +++++++++++++++++ src-docs/docs_directory.py.md | 85 +++++ src-docs/download.py.md | 33 ++ src-docs/exceptions.py.md | 135 ++++++++ src-docs/index.py.md | 162 +++++++++ src-docs/metadata.py.md | 49 +++ src-docs/migration.py.md | 70 ++++ src-docs/navigation_table.py.md | 59 ++++ src-docs/reconcile.py.md | 108 ++++++ src-docs/repository.py.md | 573 ++++++++++++++++++++++++++++++++ src-docs/sort.py.md | 40 +++ src-docs/types_.py.md | 480 ++++++++++++++++++++++++++ 20 files changed, 2595 insertions(+), 1 deletion(-) create mode 100644 src-docs/__init__.py.md create mode 100644 src-docs/action.py.md create mode 100644 src-docs/check.py.md create mode 100644 src-docs/clients.py.md create mode 100644 src-docs/commit.py.md create mode 100644 src-docs/constants.py.md create mode 100644 src-docs/content.py.md create mode 100644 src-docs/discourse.py.md create mode 100644 src-docs/docs_directory.py.md create mode 100644 src-docs/download.py.md create mode 100644 src-docs/exceptions.py.md create mode 100644 src-docs/index.py.md create mode 100644 src-docs/metadata.py.md create mode 100644 src-docs/migration.py.md create mode 100644 src-docs/navigation_table.py.md create mode 100644 src-docs/reconcile.py.md create mode 100644 src-docs/repository.py.md create mode 100644 src-docs/sort.py.md create mode 100644 src-docs/types_.py.md diff --git a/generate-src-docs.sh b/generate-src-docs.sh index e61c1e3d..b93f0074 100644 --- a/generate-src-docs.sh +++ b/generate-src-docs.sh @@ -4,4 +4,4 @@ # See LICENSE file for licensing details. rm -rf src-docs -lazydocs --no-watermark --output-path src-docs src/*.py +lazydocs --no-watermark --output-path src-docs src/gatekeeper/*.py diff --git a/src-docs/__init__.py.md b/src-docs/__init__.py.md new file mode 100644 index 00000000..07ebf0be --- /dev/null +++ b/src-docs/__init__.py.md @@ -0,0 +1,100 @@ + + + + +# module `__init__.py` +Library for uploading docs to charmhub. + +**Global Variables** +--------------- +- **DRY_RUN_NAVLINK_LINK** +- **FAIL_NAVLINK_LINK** +- **DOCUMENTATION_TAG** +- **DEFAULT_BRANCH_NAME** +- **GETTING_STARTED** + +--- + + + +## function `run_reconcile` + +```python +run_reconcile( + clients: Clients, + user_inputs: UserInputs +) → ReconcileOutputs | None +``` + +Upload the documentation to charmhub. + + + +**Args:** + + - `clients`: The clients to interact with things like discourse and the repository. + - `user_inputs`: Configurable inputs for running discourse-gatekeeper. + + + +**Returns:** + ReconcileOutputs object with the result of the action. None, if there is no reconcile. + + + +**Raises:** + + - `InputError`: if there are any problems with the contents index or executing any of the actions. + - `TaggingNotAllowedError`: if the reconcile tries to tag a branch which is not the main base branch + + +--- + + + +## function `run_migrate` + +```python +run_migrate(clients: Clients, user_inputs: UserInputs) → MigrateOutputs | None +``` + +Migrate existing docs from charmhub to local repository. + + + +**Args:** + + - `clients`: The clients to interact with things like discourse and the repository. + - `user_inputs`: Configurable inputs for running discourse-gatekeeper. + + + +**Returns:** + MigrateOutputs providing details on the action performed and a link to the Pull Request containing migrated documentation. None if there is no migration. + + +--- + + + +## function `pre_flight_checks` + +```python +pre_flight_checks(clients: Clients, user_inputs: UserInputs) → bool +``` + +Perform checks to make sure the repository is in a consistent state. + + + +**Args:** + + - `clients`: The clients to interact with things like discourse and the repository. + - `user_inputs`: Configurable inputs for running discourse-gatekeeper. + + + +**Returns:** + Boolean representing whether the checks have all been passed. + + diff --git a/src-docs/action.py.md b/src-docs/action.py.md new file mode 100644 index 00000000..4ac15b80 --- /dev/null +++ b/src-docs/action.py.md @@ -0,0 +1,60 @@ + + + + +# module `action.py` +Module for taking the required actions to match the server state with the local state. + +**Global Variables** +--------------- +- **DRY_RUN_NAVLINK_LINK** +- **DRY_RUN_REASON** +- **BASE_MISSING_REASON** +- **FAIL_NAVLINK_LINK** +- **NOT_DELETE_REASON** + +--- + + + +## function `run_all` + +```python +run_all( + actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction], + index: Index, + discourse: Discourse, + dry_run: bool, + delete_pages: bool +) → tuple[str, list[ActionReport]] +``` + +Take the actions against the server. + + + +**Args:** + + - `actions`: The actions to take. + - `index`: Information about the index. + - `discourse`: A client to the documentation server. + - `dry_run`: If enabled, only log the action that would be taken. + - `delete_pages`: Whether to delete pages that are no longer needed. + + + +**Returns:** + A 2-element tuple with the index url and the reports of all the requested action. + + +--- + +## class `UpdateCase` +The possible cases for the update action. + +Attrs: DRY_RUN: Do not make any changes. CONTENT_CHANGE: The content has been changed. BASE_MISSING: The base content is not available. DEFAULT: No other specific case applies. + + + + + diff --git a/src-docs/check.py.md b/src-docs/check.py.md new file mode 100644 index 00000000..0eb0611e --- /dev/null +++ b/src-docs/check.py.md @@ -0,0 +1,117 @@ + + + + +# module `check.py` +Module for running checks. + +**Global Variables** +--------------- +- **DOCUMENTATION_TAG** + +--- + + + +## function `get_path_with_diffs` + +```python +get_path_with_diffs( + actions: Iterable[UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction] +) → PathsWithDiff +``` + +Generate the paths that have either local or server content changes. + + + +**Args:** + + - `actions`: The update actions to track diffs for. + + + +**Returns:** + The paths that have differences. + + +--- + + + +## function `conflicts` + +```python +conflicts( + actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] +) → Iterator[Problem] +``` + +Check whether actions have any content conflicts. + +There are two types of conflicts. The first is where the local content is different to what is on the server and both the local content and the server content is different from the base. This means that there were edits on the server which have not been merged into git and the PR is making changes to the same page. + +The second type of conflict is a logical conflict which arises out of that there are at least some changes on the server that have not been merged into git yet and the branch is proposing to make changes to the documentation as well. This means that there could be changes made on the server which logically conflict with proposed changes in the PR. These conflicts can be supppressed using the discourse-ahead-ok tag on the commit that the action is running on. + + + +**Args:** + + - `actions`: The actions to check. + + + +**Yields:** + A problem for each action with a conflict + + +--- + + + +## function `external_refs` + +```python +external_refs( + index_contents: Iterable[IndexContentsListItem] +) → Iterator[Problem] +``` + +Check whether external references are valid. + +This check sends a HEAD requests and checks for a 2XX response after any redirects. + + + +**Args:** + + - `index_contents`: The contents list items to check. + + + +**Yields:** + A problem for each list item with an invalid external reference. + + +--- + +## class `PathsWithDiff` +Keeps track of paths that have any differences. + +Attrs: base_local_diffs: The paths that have a difference between the base and local content. base_server_diffs: The paths that have a difference between the local and server content. + + + + + +--- + +## class `Problem` +Details about a failed check. + +Attrs: path: Unique identifier for the file and discourse topic with the problem description: A summary of what the problem is and how to resolve it. + + + + + diff --git a/src-docs/clients.py.md b/src-docs/clients.py.md new file mode 100644 index 00000000..7152ab5d --- /dev/null +++ b/src-docs/clients.py.md @@ -0,0 +1,44 @@ + + + + +# module `clients.py` +Module for Client class. + + +--- + + + +## function `get_clients` + +```python +get_clients(user_inputs: UserInputs, base_path: Path) → Clients +``` + +Return Clients object. + + + +**Args:** + + - `user_inputs`: inputs provided via environment + - `base_path`: path where the git repository is stored + + + +**Returns:** + Clients object embedding both Discourse API and Repository clients + + +--- + +## class `Clients` +Collection of clients needed during execution. + +Attrs: discourse: Discourse client. repository: Client for the repository. + + + + + diff --git a/src-docs/commit.py.md b/src-docs/commit.py.md new file mode 100644 index 00000000..dfc3ef44 --- /dev/null +++ b/src-docs/commit.py.md @@ -0,0 +1,67 @@ + + + + +# module `commit.py` +Module for handling interactions with git commit. + + +--- + + + +## function `parse_git_show` + +```python +parse_git_show( + output: str, + repository_path: Path +) → Iterator[FileAddedOrModified | FileDeleted] +``` + +Parse the output of a git show with --name-status into manageable data. + + + +**Args:** + + - `output`: The output of the git show command. + - `repository_path`: The path to the git repository. + + + +**Yields:** + Information about each of the files that changed in the commit. + + +--- + +## class `FileAddedOrModified` +File that was added, mofied or copied copied in a commit. + + + +**Attributes:** + + - `path`: The location of the file on disk. + - `content`: The content of the file. + + + + + +--- + +## class `FileDeleted` +File that was deleted in a commit. + + + +**Attributes:** + + - `path`: The location of the file on disk. + + + + + diff --git a/src-docs/constants.py.md b/src-docs/constants.py.md new file mode 100644 index 00000000..162d0652 --- /dev/null +++ b/src-docs/constants.py.md @@ -0,0 +1,21 @@ + + + + +# module `constants.py` +Shared constants. + +The use of this module should be limited to cases where the constant is not better placed in another module or to resolve circular imports. + +**Global Variables** +--------------- +- **DEFAULT_BRANCH** +- **DOCUMENTATION_TAG** +- **DOCUMENTATION_FOLDER_NAME** +- **DOC_FILE_EXTENSION** +- **DOCUMENTATION_INDEX_FILENAME** +- **NAVIGATION_HEADING** +- **NAVIGATION_TABLE_START** +- **PATH_CHARS** + + diff --git a/src-docs/content.py.md b/src-docs/content.py.md new file mode 100644 index 00000000..d37e8961 --- /dev/null +++ b/src-docs/content.py.md @@ -0,0 +1,91 @@ + + + + +# module `content.py` +Module for checking conflicts using 3-way merge and create content based on a 3 way merge. + + +--- + + + +## function `conflicts` + +```python +conflicts(base: str, theirs: str, ours: str) → str | None +``` + +Check for merge conflicts based on the git merge algorithm. + + + +**Args:** + + - `base`: The starting point for both changes. + - `theirs`: The other change. + - `ours`: The local change. + + + +**Returns:** + The description of the merge conflicts or None if there are no conflicts. + + +--- + + + +## function `merge` + +```python +merge(base: str, theirs: str, ours: str) → str +``` + +Create the merged content based on the git merge algorithm. + + + +**Args:** + + - `base`: The starting point for both changes. + - `theirs`: The other change. + - `ours`: The local change. + + + +**Returns:** + The merged content. + + + +**Raises:** + + - `ContentError`: if there are merge conflicts. + + +--- + + + +## function `diff` + +```python +diff(first: str, second: str) → str +``` + +Show the difference between two strings. + + + +**Args:** + + - `first`: One of the strings to compare. + - `second`: One of the strings to compare. + + + +**Returns:** + The diff between the two strings. + + diff --git a/src-docs/discourse.py.md b/src-docs/discourse.py.md new file mode 100644 index 00000000..ab4b0e92 --- /dev/null +++ b/src-docs/discourse.py.md @@ -0,0 +1,300 @@ + + + + +# module `discourse.py` +Interface for Discourse interactions. + + +--- + + + +## function `create_discourse` + +```python +create_discourse( + hostname: str, + category_id: str, + api_username: str, + api_key: str +) → Discourse +``` + +Create discourse client. + + + +**Args:** + + - `hostname`: The Discourse server hostname. + - `category_id`: The category to use for topics. + - `api_username`: The discourse API username to use for interactions with the server. + - `api_key`: The discourse API key to use for interactions with the server. + + + +**Returns:** + A discourse client that is connected to the server. + + + +**Raises:** + InputError: if the api_username and api_key arguments are not strings or empty, if the protocol has been included in the hostname, the hostname is not a string or the category_id is not an integer or a string that can be converted to an integer. + + +--- + +## class `Discourse` +Interact with a discourse server. + +Attrs: host: The host of the discourse server. + + + +### function `__init__` + +```python +__init__(host: str, api_username: str, api_key: str, category_id: int) → None +``` + +Construct. + + + +**Args:** + + - `host`: The HTTP protocol and hostname for discourse (e.g., https://discourse). + - `api_username`: The username to use for API requests. + - `api_key`: The API key for requests. + - `category_id`: The category identifier to put the topics into. + + +--- + +#### property host + +The HTTP protocol and hostname for discourse (e.g., https://discourse). + + + +--- + + + +### function `absolute_url` + +```python +absolute_url(url: str) → str +``` + +Get the URL including base path for a topic. + + + +**Args:** + + - `url`: The relative or absolute URL. + + + +**Returns:** + The url with the base path. + +--- + + + +### function `check_topic_read_permission` + +```python +check_topic_read_permission(url: str) → bool +``` + +Check whether the credentials have read permission on a topic. + +Uses whether retrieve topic succeeds as indication whether the read permission is available. + + + +**Args:** + + - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. + + + +**Returns:** + Whether the credentials have read permissions to the topic. + +--- + + + +### function `check_topic_write_permission` + +```python +check_topic_write_permission(url: str) → bool +``` + +Check whether the credentials have write permission on a topic. + + + +**Args:** + + - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. + + + +**Returns:** + Whether the credentials have write permissions to the topic. + +--- + + + +### function `create_topic` + +```python +create_topic(title: str, content: str) → str +``` + +Create a new topic. + + + +**Args:** + + - `title`: The title of the topic. + - `content`: The content for the first post in the topic. + + + +**Returns:** + The URL to the topic. + + + +**Raises:** + + - `DiscourseError`: if anything goes wrong during topic creation. + +--- + + + +### function `delete_topic` + +```python +delete_topic(url: str) → str +``` + +Delete a topic. + + + +**Args:** + + - `url`: The URL to the topic. + + + +**Returns:** + The link to the deleted topic. + + + +**Raises:** + + - `DiscourseError`: if authentication fails if the server refuses to delete the topic, if the topic is not found or if anything else has gone wrong. + +--- + + + +### function `retrieve_topic` + +```python +retrieve_topic(url: str) → str +``` + +Retrieve the topic content. + + + +**Args:** + + - `url`: The URL to the topic. Assume it includes the slug and id of the topic as the last 2 elements of the url. + + + +**Returns:** + The content of the first post in the topic. + + + +**Raises:** + + - `DiscourseError`: if authentication fails, if the server refuses to return the requested topic or if the topic is not found. + +--- + + + +### function `topic_url_valid` + +```python +topic_url_valid(url: str) → _ValidationResultValid | _ValidationResultInvalid +``` + +Check whether a url to a topic is valid. Assume the url is well formatted. + +Validations: 1. The URL must start with the base path configured during construction. 2. The URL must resolve on a discourse HEAD request. 3. The URL must have 3 components in its path. 4. The first component in the path must be the literal 't'. 5. The second component in the path must be the slug to the topic which must have at least 1 character. 6. The third component must the the topic id as an integer. + + + +**Args:** + + - `url`: The URL to check. + + + +**Returns:** + Whether the URL is a valid topic URL. + +--- + + + +### function `update_topic` + +```python +update_topic( + url: str, + content: str, + edit_reason: str = 'Charm documentation updated' +) → str +``` + +Update the first post of a topic. + + + +**Args:** + + - `url`: The URL to the topic. + - `content`: The content for the first post in the topic. + - `edit_reason`: The reason the edit was made. + + + +**Returns:** + The link to the updated topic. + + + +**Raises:** + + - `DiscourseError`: if authentication fails, if the server refuses to update the first post in the topic or if the topic is not found. + + diff --git a/src-docs/docs_directory.py.md b/src-docs/docs_directory.py.md new file mode 100644 index 00000000..24b48a51 --- /dev/null +++ b/src-docs/docs_directory.py.md @@ -0,0 +1,85 @@ + + + + +# module `docs_directory.py` +Class for reading the docs directory. + +**Global Variables** +--------------- +- **DOC_FILE_EXTENSION** + +--- + + + +## function `calculate_table_path` + +```python +calculate_table_path(path_relative_to_docs: Path) → tuple[str, ] +``` + +Calculate the table path of a path. + + + +**Args:** + + - `path_relative_to_docs`: The path to calculate the table path for relative to the docs directory. + + + +**Returns:** + The relative path to the docs directory, replacing / with -, removing the extension and converting to lower case. + + +--- + + + +## function `read` + +```python +read(docs_path: Path) → Iterator[PathInfo] +``` + +Read the docs directory and return information about each directory and documentation file. + +Algorithm: 1. Get a list of all sub directories and .md files in the docs folder. 2. For each directory/ file: 2.1. Calculate the level based on the number of sub-directories to the docs directory including the docs directory. 2.2. Calculate the table path using the relative path to the docs directory, replacing / with -, removing the extension and converting to lower case. 2.3. Calculate the navlink title based on the first heading, first line if there is no heading or the file/ directory name excluding the extension with - replaced by space and titlelized if the file is empty or it is a directory. + + + +**Args:** + + - `docs_path`: The path to the docs directory containing all the documentation. + + + +**Returns:** + Information about each directory and documentation file in the docs folder. + + +--- + + + +## function `has_docs_directory` + +```python +has_docs_directory(docs_path: Path) → bool +``` + +Return existence of docs directory from base path. + + + +**Args:** + + - `docs_path`: Docs path of the repository where docs are + + + +**Returns:** + True if documentation folder exists, False otherwise + + diff --git a/src-docs/download.py.md b/src-docs/download.py.md new file mode 100644 index 00000000..22734514 --- /dev/null +++ b/src-docs/download.py.md @@ -0,0 +1,33 @@ + + + + +# module `download.py` +Library for downloading docs folder from charmhub. + + +--- + + + +## function `recreate_docs` + +```python +recreate_docs(clients: Clients, base: str) → bool +``` + +Recreate the docs folder and checks whether the docs folder is aligned with base branch/tag. + + + +**Args:** + + - `clients`: Clients object containing Repository and Discourse API clients + - `base`: tag to be compared to + + + +**Returns:** + boolean representing whether any differences have occurred + + diff --git a/src-docs/exceptions.py.md b/src-docs/exceptions.py.md new file mode 100644 index 00000000..38c61797 --- /dev/null +++ b/src-docs/exceptions.py.md @@ -0,0 +1,135 @@ + + + + +# module `exceptions.py` +Exceptions for uploading docs to charmhub. + + + +--- + +## class `ActionError` +A problem with the taking an action occurred. + + + + + +--- + +## class `BaseError` +All raised exceptions inherit from this one. + + + + + +--- + +## class `ContentError` +A problem with the content occurred. + + + + + +--- + +## class `DiscourseError` +Parent exception for all Discourse errors. + + + + + +--- + +## class `InputError` +A problem with the user input occurred. + + + + + +--- + +## class `MigrationError` +A problem with migration occurred. + + + + + +--- + +## class `NavigationTableParseError` +A problem with the navigation table parsing occurred. + + + + + +--- + +## class `PagePermissionError` +A required permission is not available on a page. + + + + + +--- + +## class `ReconcilliationError` +A problem with the reconcilliation occurred. + + + + + +--- + +## class `RepositoryClientError` +A problem with git repository client occurred. + + + + + +--- + +## class `RepositoryFileNotFoundError` +A problem retrieving a file from a git repository occurred. + + + + + +--- + +## class `RepositoryTagNotFoundError` +A problem retrieving a tag from a git repository occurred. + + + + + +--- + +## class `ServerError` +A problem with the server storing the documentation occurred. + + + + + +--- + +## class `TaggingNotAllowedError` +The commit cannot be tagged as it is outside of the main. + + + + + diff --git a/src-docs/index.py.md b/src-docs/index.py.md new file mode 100644 index 00000000..54059347 --- /dev/null +++ b/src-docs/index.py.md @@ -0,0 +1,162 @@ + + + + +# module `index.py` +Execute the uploading of documentation. + +**Global Variables** +--------------- +- **DOC_FILE_EXTENSION** +- **DOCUMENTATION_INDEX_FILENAME** +- **NAVIGATION_HEADING** +- **CONTENTS_HEADER** +- **CONTENTS_END_LINE_PREFIX** + +--- + + + +## function `get` + +```python +get(metadata: Metadata, docs_path: Path, server_client: Discourse) → Index +``` + +Retrieve the local and server index information. + + + +**Args:** + + - `metadata`: Information about the charm. + - `docs_path`: The base path to look for the documentation. + - `server_client`: A client to the documentation server. + + + +**Returns:** + The index page. + + + +**Raises:** + + - `ServerError`: if interactions with the documentation server occurs. + + +--- + + + +## function `contents_from_page` + +```python +contents_from_page(page: str) → str +``` + +Get index file contents from server page. + + + +**Args:** + + - `page`: Page contents from server. + + + +**Returns:** + Index file contents. + + +--- + + + +## function `get_content_for_server` + +```python +get_content_for_server(index_file: IndexFile) → str +``` + +Get the contents from the index file that should be passed to the server. + + + +**Args:** + + - `index_file`: Information about the local index file. + + + +**Returns:** + The contents of the index file that should be stored on the server. + + +--- + + + +## function `classify_item_reference` + +```python +classify_item_reference( + reference: str, + docs_path: Path +) → +``` + +Classify the type of a reference. + + + +**Args:** + + - `reference`: The reference to classify. + - `docs_path`: The parent path of the reference. + + + +**Returns:** + The type of the reference. + + +--- + + + +## function `get_contents` + +```python +get_contents( + index_file: IndexFile, + docs_path: Path +) → Iterator[IndexContentsListItem] +``` + +Get the contents list items from the index file. + + + +**Args:** + + - `index_file`: The index file to read the contents from. + - `docs_path`: The base directory of all items. + + + +**Returns:** + Iterator with all items from the contents list. + + +--- + +## class `ItemReferenceType` +Classification for the path of an item. + +Attrs: EXTERNAL: a link to an external resource. DIR: a link to a local directory. FILE: a link to a local file. UNKNOWN: The reference is not a known type. + + + + + diff --git a/src-docs/metadata.py.md b/src-docs/metadata.py.md new file mode 100644 index 00000000..65d0958a --- /dev/null +++ b/src-docs/metadata.py.md @@ -0,0 +1,49 @@ + + + + +# module `metadata.py` +Module for parsing metadata.yaml file. + +**Global Variables** +--------------- +- **CHARMCRAFT_FILENAME** +- **CHARMCRAFT_NAME_KEY** +- **CHARMCRAFT_LINKS_KEY** +- **CHARMCRAFT_LINKS_DOCS_KEY** +- **METADATA_DOCS_KEY** +- **METADATA_FILENAME** +- **METADATA_NAME_KEY** + +--- + + + +## function `get` + +```python +get(path: Path) → Metadata +``` + +Check for and read the metadata. + +The charm metadata can be in the file metadata.yaml or in charmcraft.yaml. From charmcraft version 2.5, the information should be in charmcraft.yaml, and the user should only modify that file. This function does not consider the case in which the name is in one file and the doc link is in the other. + + + +**Args:** + + - `path`: The base path to look for the metadata files. + + + +**Returns:** + The contents of the metadata file. + + + +**Raises:** + + - `InputError`: if the metadata file does not exist or is malformed. + + diff --git a/src-docs/migration.py.md b/src-docs/migration.py.md new file mode 100644 index 00000000..e6c78211 --- /dev/null +++ b/src-docs/migration.py.md @@ -0,0 +1,70 @@ + + + + +# module `migration.py` +Module for migrating remote documentation into local git repository. + +**Global Variables** +--------------- +- **EMPTY_DIR_REASON** +- **GITKEEP_FILENAME** + +--- + + + +## function `make_parent` + +```python +make_parent(docs_path: Path, document_meta: MigrationFileMeta) → Path +``` + +Construct path leading to document to be created. + + + +**Args:** + + - `docs_path`: Path to documentation directory. + - `document_meta`: Information about document to be migrated. + + + +**Returns:** + Full path to the parent directory of the document to be migrated. + + +--- + + + +## function `run` + +```python +run( + table_rows: Iterable[TableRow], + index_content: str, + discourse: Discourse, + docs_path: Path +) → None +``` + +Write table contents to the document directory. + + + +**Args:** + + - `table_rows`: Iterable sequence of documentation structure to be migrated. + - `index_content`: Main content describing the charm. + - `discourse`: Client to the documentation server. + - `docs_path`: The path to the docs directory containing all the documentation. + + + +**Raises:** + + - `MigrationError`: if any migration report has failed. + + diff --git a/src-docs/navigation_table.py.md b/src-docs/navigation_table.py.md new file mode 100644 index 00000000..3f752b6d --- /dev/null +++ b/src-docs/navigation_table.py.md @@ -0,0 +1,59 @@ + + + + +# module `navigation_table.py` +Module for parsing and rendering a navigation table. + + +--- + + + +## function `from_page` + +```python +from_page(page: str, discourse: Discourse) → Iterator[TableRow] +``` + +Create an instance based on a markdown page. + +Algorithm: 1. Extract the table based on a regular expression looking for a 3 column table with the headers level, path and navlink (case insensitive). If the table is not found, assume that it is equivalent to a table without rows. 2. Process the rows line by line: 2.1. If the row matches the header or filler pattern, skip it. 2.2. Extract the level, path and navlink values. + + + +**Args:** + + - `page`: The page to extract the rows from. + - `discourse`: API to the Discourse server. + + + +**Returns:** + The parsed rows from the table. + + +--- + + + +## function `generate_table_row` + +```python +generate_table_row(lines: Sequence[str]) → Iterator[TableRow] +``` + +Return an iterator with the TableRows representing the parsed table lines. + + + +**Args:** + + - `lines`: list of strings representing the different lines. + + + +**Yields:** + parsed TableRow object, representing the row of the table + + diff --git a/src-docs/reconcile.py.md b/src-docs/reconcile.py.md new file mode 100644 index 00000000..b4bf0aae --- /dev/null +++ b/src-docs/reconcile.py.md @@ -0,0 +1,108 @@ + + + + +# module `reconcile.py` +Module for calculating required changes based on docs directory and navigation table. + +**Global Variables** +--------------- +- **DOCUMENTATION_TAG** +- **NAVIGATION_TABLE_START** + +--- + + + +## function `is_same_content` + +```python +is_same_content( + index: Index, + actions: Iterable[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] +) → bool +``` + +Check if the content on Discourse and Github matches. + + + +**Args:** + + - `index`: Index object representing local and server content for the index file + - `actions`: List of actions representing what the reconcile action would do over all topics + + + +**Returns:** + Boolean true if the contents match, false otherwise + + +--- + + + +## function `run` + +```python +run( + sorted_path_infos: Iterable[PathInfo | IndexContentsListItem], + table_rows: Iterable[TableRow], + clients: Clients, + base_path: Path +) → Iterator[CreateGroupAction | CreatePageAction | CreateExternalRefAction | NoopGroupAction | NoopPageAction | NoopExternalRefAction | UpdateGroupAction | UpdatePageAction | UpdateExternalRefAction | DeleteGroupAction | DeletePageAction | DeleteExternalRefAction] +``` + +Reconcile differences between the docs directory and documentation server. + +Preserves the order of path_infos although does not for items only in table_rows. + +This function needs to match files and directories locally to items on the navigation table on the server knowing that there may be cases that are not matched. The navigation table relies on the order that items are displayed to figure out the hierarchy/ page grouping (this is not a design choice of this action but how the documentation is interpreted by charmhub). Assume the `path_infos` have been sorted to ensure that the hierarchy will be calculated correctly by the server when the new navigation table is generated. + +Items only in table_rows won't have their order preserved. Those items are the items that are only on the server, i.e., those keys will just result in delete actions which have no effect on the navigation table that is generated and hence ordering for them doesn't matter. + + + +**Args:** + + - `base_path`: The base path of the repository. + - `sorted_path_infos`: Information about the local documentation files. + - `table_rows`: Rows from the navigation table. + - `clients`: The clients to interact with things like discourse and the repository. + + + +**Returns:** + The actions required to reconcile differences between the documentation server and local files. + + +--- + + + +## function `index_page` + +```python +index_page( + index: Index, + table_rows: Iterable[TableRow], + discourse: Discourse +) → CreateIndexAction | NoopIndexAction | UpdateIndexAction +``` + +Reconcile differences for the index page. + + + +**Args:** + + - `index`: Information about the index on the server and locally. + - `table_rows`: The current navigation table rows based on local files. + - `discourse`: A client to the documentation server. + + + +**Returns:** + The action to take for the index page. + + diff --git a/src-docs/repository.py.md b/src-docs/repository.py.md new file mode 100644 index 00000000..650c239a --- /dev/null +++ b/src-docs/repository.py.md @@ -0,0 +1,573 @@ + + + + +# module `repository.py` +Module for handling interactions with git repository. + +**Global Variables** +--------------- +- **DOCUMENTATION_FOLDER_NAME** +- **GITHUB_HOSTNAME** +- **ORIGIN_NAME** +- **ACTIONS_USER_NAME** +- **ACTIONS_USER_EMAIL** +- **ACTIONS_PULL_REQUEST_TITLE** +- **ACTIONS_PULL_REQUEST_BODY** +- **PR_LINK_NO_CHANGE** +- **TAG_MESSAGE** +- **CONFIG_USER_SECTION_NAME** +- **CONFIG_USER_NAME** +- **CONFIG_USER_EMAIL** +- **BRANCH_PREFIX** +- **DEFAULT_BRANCH_NAME** +- **ACTIONS_COMMIT_MESSAGE** + +--- + + + +## function `create_repository_client` + +```python +create_repository_client( + access_token: str | None, + base_path: Path, + charm_dir: str = '' +) → Client +``` + +Create a Github instance to handle communication with Github server. + + + +**Args:** + + - `access_token`: Access token that has permissions to open a pull request. + - `base_path`: Path where local .git resides in. + - `charm_dir`: Relative directory where the charm files are located. + + + +**Raises:** + + - `InputError`: if invalid access token or invalid git remote URL is provided. + + + +**Returns:** + A Github repository instance. + + +--- + +## class `Client` +Wrapper for git/git-server related functionalities. + +Attrs: base_path: The root directory of the repository. base_charm_path: The directory of the repository where the charm is. docs_path: The directory of the repository where the documentation is. metadata: Metadata object of the charm has_docs_directory: whether the repository has a docs directory current_branch: current git branch used in the repository current_commit: current commit checkout in the repository branches: list of all branches + + + +### function `__init__` + +```python +__init__( + repository: Repo, + github_repository: Repository, + charm_dir: str = '' +) → None +``` + +Construct. + + + +**Args:** + + - `repository`: Client for interacting with local git repository. + - `github_repository`: Client for interacting with remote github repository. + - `charm_dir`: Relative directory where charm files are located. + + +--- + +#### property base_charm_path + +Return the Path of the charm in the repository. + + + +**Returns:** + Path of the repository. + +--- + +#### property branches + +Return all local branches. + +--- + +#### property current_branch + +Return the current branch. + +--- + +#### property current_commit + +Return the current branch. + +--- + +#### property docs_path + +Return the Path of the charm in the repository. + + + +**Returns:** + Path of the repository. + +--- + +#### property has_docs_directory + +Return whether the repository has a docs directory. + +--- + +#### property metadata + +Return the Metadata object of the charm. + + + +--- + + + +### function `create_branch` + +```python +create_branch(branch_name: str, base: str | None = None) → Client +``` + +Create a new branch. + +Note that this will not switch branch. To create and switch branch, please pipe the two operations together: + +repository.create_branch(branch_name).switch(branch_name) + + + +**Args:** + + - `branch_name`: name of the branch to be created + - `base`: branch or tag to be branched from + + + +**Raises:** + + - `RepositoryClientError`: if an error occur when creating a new branch + + + +**Returns:** + Repository client object. + +--- + + + +### function `create_pull_request` + +```python +create_pull_request(base: str) → PullRequest +``` + +Create pull request for changes in given repository path. + + + +**Args:** + + - `base`: tag or branch against to which the PR is opened + + + +**Raises:** + + - `InputError`: when the repository is not dirty, hence resulting on an empty pull-request + + + +**Returns:** + Pull request object + +--- + + + +### function `get_file_content_from_tag` + +```python +get_file_content_from_tag(path: str, tag_name: str) → str +``` + +Get the content of a file for a specific tag. + + + +**Args:** + + - `path`: The path to the file. + - `tag_name`: The name of the tag. + + + +**Returns:** + The content of the file for the tag. + + + +**Raises:** + + - `RepositoryTagNotFoundError`: if the tag could not be found in the repository. + - `RepositoryFileNotFoundError`: if the file could not be retrieved from GitHub, more than one file is returned or a non-file is returned + - `RepositoryClientError`: if there is a problem with communicating with GitHub + +--- + + + +### function `get_pull_request` + +```python +get_pull_request(branch_name: str) → PullRequest | None +``` + +Return open pull request matching the provided branch name. + + + +**Args:** + + - `branch_name`: branch name to select open pull requests. + + + +**Raises:** + + - `RepositoryClientError`: if more than one PR is open with the given branch name + + + +**Returns:** + PullRequest object. If no PR is found, None is returned. + +--- + + + +### function `get_summary` + +```python +get_summary(directory: str | Path | None) → DiffSummary +``` + +Return a summary of the differences against the most recent commit. + + + +**Args:** + + - `directory`: constraint committed changes to a particular folder only. If None, all the folders are committed. Default is the documentation folder. + + + +**Returns:** + DiffSummary object representing the summary of the differences. + +--- + + + +### function `is_commit_in_branch` + +```python +is_commit_in_branch(commit_sha: str, branch: str | None = None) → bool +``` + +Check if commit exists in a given branch. + + + +**Args:** + + - `commit_sha`: SHA of the commit to be searched for + - `branch`: name of the branch against which the check is done. When None, the current branch is used. + + + +**Raises:** + + - `RepositoryClientError`: when the commit is not found in the repository + + + +**Returns:** + boolean representing whether the commit exists in the branch + +--- + + + +### function `is_dirty` + +```python +is_dirty(branch_name: str | None = None) → bool +``` + +Check if repository path has any changes including new files. + + + +**Args:** + + - `branch_name`: name of the branch to be checked against dirtiness + + + +**Returns:** + True if any changes have occurred. + +--- + + + +### function `is_same_commit` + +```python +is_same_commit(tag: str, commit: str) → bool +``` + +Return whether tag and commit coincides. + + + +**Args:** + + - `tag`: name of the tag + - `commit`: sha of the commit + + + +**Returns:** + True if the two pointers coincides, False otherwise. + +--- + + + +### function `pull` + +```python +pull(branch_name: str | None = None) → None +``` + +Pull content from remote for the provided branch. + + + +**Args:** + + - `branch_name`: branch to be pulled from the remote + +--- + + + +### function `switch` + +```python +switch(branch_name: str) → Client +``` + +Switch branch for the repository. + + + +**Args:** + + - `branch_name`: name of the branch to switch to. + + + +**Returns:** + Repository object with the branch switched. + +--- + + + +### function `tag_commit` + +```python +tag_commit(tag_name: str, commit_sha: str) → None +``` + +Tag a commit, if the tag already exists, it is deleted first. + + + +**Args:** + + - `tag_name`: The name of the tag. + - `commit_sha`: The SHA of the commit to tag. + + + +**Raises:** + + - `RepositoryClientError`: if there is a problem with communicating with GitHub + +--- + + + +### function `tag_exists` + +```python +tag_exists(tag_name: str) → str | None +``` + +Check if a given tag exists. + + + +**Args:** + + - `tag_name`: name of the tag to be checked for existence + + + +**Returns:** + hash of the commit the tag refers to. + +--- + + + +### function `update_branch` + +```python +update_branch( + commit_msg: str, + directory: str | Path | None, + push: bool = True, + force: bool = False +) → Client +``` + +Update branch with a new commit. + + + +**Args:** + + - `commit_msg`: commit message to be committed to the branch + - `push`: push new changes to remote branches + - `force`: when pushing to remove, use force flag + - `directory`: constraint committed changes to a particular folder only. If None, all the folders are committed. Default is the documentation folder. + + + +**Raises:** + + - `RepositoryClientError`: if any error are encountered in the update process + + + +**Returns:** + Repository client with the updated branch + +--- + + + +### function `update_pull_request` + +```python +update_pull_request(branch: str) → None +``` + +Update and push changes to the given branch. + + + +**Args:** + + - `branch`: name of the branch to be updated + +--- + + + +### function `with_branch` + +```python +with_branch(branch_name: str) → Iterator['Client'] +``` + +Return a context for operating within the given branch. + +At the end of the 'with' block, the branch is switched back to what it was initially. + + + +**Args:** + + - `branch_name`: name of the branch + + + +**Yields:** + Context to operate on the provided branch + + +--- + +## class `DiffSummary` +Class representing the summary of the dirty status of a repository. + +Attrs: is_dirty: boolean indicated whether there is any delta new: list of files added in the delta removed: list of files removed in the delta modified: list of files modified in the delta + + + + +--- + + + +### classmethod `from_raw_diff` + +```python +from_raw_diff(diffs: Sequence[Diff]) → DiffSummary +``` + +Return a DiffSummary class from a sequence of git.Diff objects. + + + +**Args:** + + - `diffs`: list of git.Diff objects representing the delta between two snapshots. + + + +**Returns:** + DiffSummary class + + diff --git a/src-docs/sort.py.md b/src-docs/sort.py.md new file mode 100644 index 00000000..65e7e6a2 --- /dev/null +++ b/src-docs/sort.py.md @@ -0,0 +1,40 @@ + + + + +# module `sort.py` +Sort items for publishing. + + +--- + + + +## function `using_contents_index` + +```python +using_contents_index( + path_infos: Iterable[PathInfo], + index_contents: Iterable[IndexContentsListItem], + docs_path: Path +) → Iterator[PathInfo | IndexContentsListItem] +``` + +Sort PathInfos based on the contents index and alphabetical rank. + +Also updates the navlink title for any items matched to the contents index. + + + +**Args:** + + - `path_infos`: Information about the local documentation files. + - `index_contents`: The content index items used to apply sorting. + - `docs_path`: The directory the documentation files are contained within. + + + +**Yields:** + PathInfo sorted based on their location on the contents index and then by alphabetical rank. + + diff --git a/src-docs/types_.py.md b/src-docs/types_.py.md new file mode 100644 index 00000000..a4c6a180 --- /dev/null +++ b/src-docs/types_.py.md @@ -0,0 +1,480 @@ + + + + +# module `types_.py` +Types for uploading docs to charmhub. + + + +--- + +## class `ActionReport` +Post execution report for an action. + +Attrs: table_row: The navigation table entry, None for delete or index actions. location: The URL that the action operated on, None for groups or if a create action was skipped, if running in reconcile mode. Path to migrated file, if running in migration mode. None on action failure. result: The action execution result. reason: The reason, None for success reports. + + + + + +--- + +## class `ActionResult` +Result of taking an action. + +Attrs: SUCCESS: The action succeeded. SKIP: The action was skipped. FAIL: The action failed. + + + + + +--- + +## class `ContentChange` +Represents a change to the content. + +Attrs: base: The content which is the base for comparison. server: The content on the server. local: The content on the local disk. + + + + + +--- + +## class `CreateExternalRefAction` +Represents a external reference to be created. + +Attrs: navlink_value: The external reference. + + + + + +--- + +## class `CreateGroupAction` +Represents a group to be created. + + + + + +--- + +## class `CreateIndexAction` +Represents an index page to be created. + +Attrs: title: The title of the index page. content: The content including the navigation table. + + + + + +--- + +## class `CreatePageAction` +Represents a page to be created. + +Attrs: content: The documentation content. + + + + + +--- + +## class `DeleteExternalRefAction` +Represents an external reference to be deleted. + + + + + +--- + +## class `DeleteGroupAction` +Represents a group to be deleted. + + + + + +--- + +## class `DeletePageAction` +Represents a page to be deleted. + +Attrs: content: The documentation content. + + + + + +--- + +## class `DocumentMeta` +Represents a document to be migrated from the index table. + +Attrs: link: Link to content to read from. table_row: Document row that is the source of document file. + + + + + +--- + +## class `GitkeepMeta` +Represents an empty directory from the index table. + +Attrs: table_row: Empty group row that is the source of .gitkeep file. + + + + + +--- + +## class `Index` +Information about the local and server index page. + +Attrs: server: The index page on the server. local: The local index file contents. name: The name of the charm. + + + + + +--- + +## class `IndexContentChange` +Represents a change to the content of the index. + +Attrs: old: The previous content. new: The new content. + + + + + +--- + +## class `IndexContentsListItem` +Represents an item in the contents table. + +Attrs: hierarchy: The number of parent items to the root of the list reference_title: The name of the reference reference_value: The link to the referenced item rank: The number of preceding elements in the list at any hierarchy hidden: Whether the item should be displayed on the navigation table table_path: The path for the item on the table. is_external: Whether the item is an external reference. + + +--- + +#### property is_external + +Whether the row is an external reference. + + + +**Returns:** + Whether the item in the table is an external item. + +--- + +#### property table_path + +The table path for the item. + +In the case of a HTTP reference, changes http://canonical.com/1 to http,canonical,com,1 removing the HTTP protocol characters so that the path conforms to the path in the non-HTTP case. Any remaining characters not allowed in the path are also removed. For a non-HTTP case, removes the file suffix and splits on / to built the path. + + + +**Returns:** + The table path for the item. + + + + +--- + +## class `IndexDocumentMeta` +Represents an index file document. + +Attrs: content: Contents to write to index file. + + + + + +--- + +## class `IndexFile` +Information about a documentation page. + +Attrs: title: The title for the index. content: The local content of the index. + + + + + +--- + +## class `Metadata` +Information within metadata file. Refer to: https://juju.is/docs/sdk/metadata-yaml. + +Only name and docs are the fields of interest for the scope of this module. + +Attrs: name: Name of the charm. docs: A link to a documentation cover page on Discourse. + + + + + +--- + +## class `MigrateOutputs` +Output provided by the reconcile workflow. + +Attrs: action: Action taken on the PR pull_request_url: url of the pull-request when relevant + + + + + +--- + +## class `MigrationFileMeta` +Metadata about a document to be migrated. + +Attrs: path: The full document path to be written to. + + + + + +--- + +## class `Navlink` +Represents navlink of a table row of the navigation table. + +Attrs: title: The title of the documentation page. link: The relative URL to the documentation page or None if there is no link. hidden: Whether the item should be displayed on the navigation table. + + + + + +--- + +## class `NavlinkChange` +Represents a change to the navlink. + +Attrs: old: The previous navlink. new: The new navlink. + + + + + +--- + +## class `NoopExternalRefAction` +Represents an external reference with no required changes. + + + + + +--- + +## class `NoopGroupAction` +Represents a group with no required changes. + + + + + +--- + +## class `NoopIndexAction` +Represents an index page with no required changes. + +Attrs: content: The content including the navigation table. url: The URL to the index page. + + + + + +--- + +## class `NoopPageAction` +Represents a page with no required changes. + +Attrs: content: The documentation content of the page. + + + + + +--- + +## class `Page` +Information about a documentation page. + +Attrs: url: The link to the page. content: The documentation text of the page. + + + + + +--- + +## class `PathInfo` +Represents a file or directory in the docs directory. + +Attrs: local_path: The path to the file on the local disk. level: The number of parent directories to the docs folder including the docs folder. table_path: The computed table path based on the disk path relative to the docs folder. navlink_title: The title of the navlink. alphabetical_rank: The rank of the path info based on alphabetically sorting all relevant path infos. navlink_hidden: Whether the item should be displayed on the navigation table + + + + + +--- + +## class `PullRequestAction` +Result of taking an action. + +Attrs: OPENED: A new PR has been opened. CLOSED: An existing PR has been closed. UPDATED: An existing PR has been updated. + + + + + +--- + +## class `ReconcileOutputs` +Output provided by the reconcile workflow. + +Attrs: index_url: url with the root documentation topic on Discourse topics: List of urls with actions documentation_tag: commit sha to which the tag was created + + + + + +--- + +## class `TableRow` +Represents one parsed row of the navigation table. + +Attrs: level: The number of parents, is 1 if there is no parent. path: The a unique string identifying the row. navlink: The title and relative URL to the documentation page. is_group: Whether the row is the parent of zero or more other rows. + + +--- + +#### property is_group + +Whether the row is a group of pages. + + + +--- + + + +### function `is_external` + +```python +is_external(server_hostname: str) → bool +``` + +Whether the row is an external reference. + + + +**Args:** + + - `server_hostname`: The hostname of the discourse server. + + + +**Returns:** + Whether the item in the table is an external item. + +--- + + + +### function `to_markdown` + +```python +to_markdown(server_hostname: str) → str +``` + +Convert to a line in the navigation table. + + + +**Args:** + + - `server_hostname`: The hostname of the discourse server. + + + +**Returns:** + The line in the navigation table. + + +--- + +## class `UpdateExternalRefAction` +Represents an external reference to be updated. + + + + + +--- + +## class `UpdateGroupAction` +Represents a group to be updated. + + + + + +--- + +## class `UpdateIndexAction` +Represents an index page to be updated. + +Attrs: content_change: The change to the content including the navigation table. url: The URL to the index page. + + + + + +--- + +## class `UpdatePageAction` +Represents a page to be updated. + +Attrs: content_change: The change to the documentation content. + + + + + +--- + +## class `UserInputs` +Configurable user input values used to run discourse-gatekeeper. + +Attrs: discourse: The configuration for interacting with discourse. dry_run: If enabled, only log the action that would be taken. Has no effect in migration mode. delete_pages: Whether to delete pages that are no longer needed. Has no effect in migration mode. github_access_token: A Personal Access Token(PAT) or access token with repository access. Required in migration mode. commit_sha: The SHA of the commit the action is running on. base_branch: The main branch against which the syncs act on. charm_dir: Directory the charm is located in. + + + + + +--- + +## class `UserInputsDiscourse` +Configurable user input values used to run discourse-gatekeeper. + +Attrs: hostname: The base path to the discourse server. category_id: The category identifier to use on discourse for all topics. api_username: The discourse API username to use for interactions with the server. api_key: The discourse API key to use for interactions with the server. + + + + + From 6066cbb270e4543d198bfc8686db9029c9afb525 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 11:00:32 +0200 Subject: [PATCH 3/7] specify requirements.txt in dependencies --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6fcd4f99..f98289e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,10 @@ [project] name = "discourse-gatekeeper" version = "0.0.1" +dynamic = ["dependencies"] +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} [tool.bandit] exclude_dirs = ["/venv/", ".tox"] From c8a569d6a619901e3e41af392a064a0c80611db7 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 11:01:41 +0200 Subject: [PATCH 4/7] put requirements in project.toml --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index abe98b97..c1a55904 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,6 @@ RUN apt-get update && \ RUN mkdir /usr/src/app WORKDIR /usr/src/app -COPY requirements.txt /usr/src/app -RUN pip install --no-cache-dir -r requirements.txt COPY . /usr/src/app RUN pip install /usr/src/app From 4c8f17405c59c20096468a97557a6920522e8067 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 12:04:55 +0200 Subject: [PATCH 5/7] add --no-cache-dir to pip in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c1a55904..2080f03a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN mkdir /usr/src/app WORKDIR /usr/src/app COPY . /usr/src/app -RUN pip install /usr/src/app +RUN pip install --no-cache-dir /usr/src/app ENV PYTHONPATH /usr/src/app CMD ["/usr/src/app/main.py"] From a28445f7c6ccaee888206e8123febc29cda9177b Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 9 Apr 2024 12:06:42 +0200 Subject: [PATCH 6/7] update changelog --- CHANGELOG.md | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87ba2e92..ae876ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Project prepared to be installed with `pip install`, so it can be reused in + the repository https://github.com/canonical/gatekeeper-repo-test + ## [v0.9.0] - 2024-04-04 ### Added diff --git a/pyproject.toml b/pyproject.toml index f98289e7..fd852d9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "discourse-gatekeeper" -version = "0.0.1" +version = "0.9.0" dynamic = ["dependencies"] [tool.setuptools.dynamic] From 856e1bc20c48c1b564c8f2fb5ad19031c1db4536 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 10 Apr 2024 14:17:52 +0200 Subject: [PATCH 7/7] add newline to .dockerignore --- .dockerignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index faed0abb..6df235b6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,4 +5,4 @@ !/main.py !/requirements.txt !/src -!/pyproject.toml \ No newline at end of file +!/pyproject.toml