Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce strict Optional typing #723

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ repos:
rev: v0.961
hooks:
- id: mypy
args: [--no-strict-optional, --ignore-missing-imports]
args: [--ignore-missing-imports]
additional_dependencies:
[types-pkg_resources, types-requests, types-Deprecated]
- repo: https://github.com/packit/pre-commit-hooks
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ check:
PYTHONPATH=$(CURDIR) PYTHONDONTWRITEBYTECODE=1 pytest --verbose --showlocals $(TEST_TARGET)

check-in-container:
podman run --rm -it -v $(CURDIR):/src:Z -w /src --env TEST_TARGET $(OGR_IMAGE) make -e GITHUB_TOKEN=$(GITHUB_TOKEN) GITLAB_TOKEN=$(GITLAB_TOKEN) check
podman run --rm -it -v $(CURDIR):/src:Z -w /src --env TEST_TARGET $(OGR_IMAGE) \
make -e GITHUB_TOKEN=$(GITHUB_TOKEN) GITLAB_TOKEN=$(GITLAB_TOKEN) \
PAGURE_TOKEN=$(PAGURE_TOKEN) PAGURE_OGR_TEST_TOKEN=$(PAGURE_OGR_TEST_TOKEN) check

shell:
podman run --rm -ti -v $(CURDIR):/src:Z -w /src $(OGR_IMAGE) bash
Expand Down
32 changes: 19 additions & 13 deletions ogr/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def body(self, new_body: str) -> None:
self._body = new_body

@property
def id(self) -> int:
def id(self) -> Optional[int]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense though?

I think I know what you meant in the description.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a bit of a dilemma. It doesn't make much sense semantically, however just based on our interface, this could happen. If I wanted to enforce an int here, the order of arguments in the constructor would have to be changed (along with some other changes of course)

return self._id

@property
Expand All @@ -226,12 +226,12 @@ def author(self) -> str:
return self._author

@property
def created(self) -> datetime.datetime:
def created(self) -> Optional[datetime.datetime]:
"""Datetime of creation of the comment."""
return self._created

@property
def edited(self) -> datetime.datetime:
def edited(self) -> Optional[datetime.datetime]:
"""Datetime of last edit of the comment."""
return self._edited

Expand All @@ -256,7 +256,7 @@ def add_reaction(self, reaction: str) -> Reaction:

class IssueComment(Comment):
@property
def issue(self) -> "Issue":
def issue(self) -> Optional["Issue"]:
"""Issue of issue comment."""
return self._parent

Expand All @@ -266,7 +266,7 @@ def __str__(self) -> str:

class PRComment(Comment):
@property
def pull_request(self) -> "PullRequest":
def pull_request(self) -> Optional["PullRequest"]:
"""Pull request of pull request comment."""
return self._parent

Expand Down Expand Up @@ -642,12 +642,12 @@ def head_commit(self) -> str:
raise NotImplementedError()

@property
def target_branch_head_commit(self) -> str:
def target_branch_head_commit(self) -> Optional[str]:
"""Commit hash of the HEAD commit of the target branch."""
raise NotImplementedError()

@property
def merge_commit_sha(self) -> str:
def merge_commit_sha(self) -> Optional[str]:
"""
Commit hash of the merge commit of the pull request.

Expand Down Expand Up @@ -1014,7 +1014,7 @@ def created(self) -> datetime.datetime:
raise NotImplementedError()

@property
def edited(self) -> datetime.datetime:
def edited(self) -> Optional[datetime.datetime]:
"""Datetime of editing the commit status."""
raise NotImplementedError()

Expand Down Expand Up @@ -1112,7 +1112,11 @@ def body(self) -> str:

@property
def git_tag(self) -> GitTag:
"""Object that represents tag tied to the release."""
"""Object that represents tag tied to the release.

Raises:
OgrException, if the tag is not found.
"""
raise NotImplementedError()

@property
Expand Down Expand Up @@ -1230,7 +1234,7 @@ class GitService(OgrAbstractClass):
instance_url (str): URL of the git forge instance.
"""

instance_url: Optional[str] = None
instance_url: str

def __init__(self, **_: Any) -> None:
pass
Expand Down Expand Up @@ -1326,7 +1330,9 @@ def list_projects(


class GitProject(OgrAbstractClass):
def __init__(self, repo: str, service: GitService, namespace: str) -> None:
def __init__(
self, repo: str, service: GitService, namespace: Optional[str]
) -> None:
"""
Args:
repo: Name of the project.
Expand All @@ -1335,7 +1341,7 @@ def __init__(self, repo: str, service: GitService, namespace: str) -> None:

- GitHub: username or org name.
- GitLab: username or org name.
- Pagure: namespace (e.g. `"rpms"`).
- Pagure: namespace (e.g. `"rpms"`). May be `None`, i.e. no namespace present.
Comment on lines -1338 to +1344
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using an empty string for this?

(I am looking at that from the GitLab perspecive where you can have "one_namespace", "two/namespaces", so why not "".)

I know, it's a functional change...;)


In case of forks: `"fork/{username}/{namespace}"`.
"""
Expand Down Expand Up @@ -1916,7 +1922,7 @@ def get_username(self) -> str:
"""
raise NotImplementedError()

def get_email(self) -> str:
def get_email(self) -> Optional[str]:
"""
Returns:
Email of the user.
Expand Down
12 changes: 11 additions & 1 deletion ogr/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def get_project(

Returns:
`GitProject` using the matching implementation.

Raises:
OgrException, if the url failed to be parsed or no matching
project type was found.

"""
mapping = service_mapping_update.copy() if service_mapping_update else {}
custom_instances = custom_instances or []
Expand All @@ -88,6 +93,8 @@ def get_project(

kls = get_service_class(url=url, service_mapping_update=mapping)
parsed_repo_url = parse_git_repo(url)
if not parsed_repo_url:
raise OgrException(f"Failed to parse url '{url}', invalid format.")

service = None
if custom_instances:
Expand Down Expand Up @@ -131,8 +138,11 @@ def get_service_class_or_none(
mapping.update(service_mapping_update)

parsed_url = parse_git_repo(url)
if not parsed_url:
return None

for service, service_kls in mapping.items():
if service in parsed_url.hostname:
if parsed_url.hostname and service in parsed_url.hostname:
return service_kls

return None
Expand Down
14 changes: 7 additions & 7 deletions ogr/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ class RepoUrl:
Class that represents repo URL.

Attributes:
repo (str): Name of the repository.
namespace (Optional[str]): Namespace of the repository, if has any.
username (Optional[str]): Username of the repository owner, if can be
repo: Name of the repository. Can be `None` if parsing is being done.
namespace: Namespace of the repository, if has any.
username: Username of the repository owner, if can be
specified.
is_fork (bool): Flag denoting if repository is a fork, if can be
is_fork: Flag denoting if repository is a fork, if can be
specified (Pagure).
hostname (Optional[str]): Hostname of host of the repository.
scheme (Optional[str]): Protocol used to access repository.
hostname: Hostname of host of the repository.
scheme: Protocol used to access repository.
"""

def __init__(
self,
repo: str,
repo: Optional[str],
namespace: Optional[str] = None,
username: Optional[str] = None,
is_fork: bool = False,
Expand Down
2 changes: 1 addition & 1 deletion ogr/services/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def full_repo_name(self) -> str:

class BasePullRequest(PullRequest):
@property
def target_branch_head_commit(self) -> str:
def target_branch_head_commit(self) -> Optional[str]:
return self.target_project.get_sha_from_branch(self.target_branch)

def get_comments(
Expand Down
5 changes: 3 additions & 2 deletions ogr/services/github/auth_providers/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class GithubAuthentication:
Represents a token manager for authentication via GitHub App.
"""

def get_token(self, namespace: str, repo: str) -> str:
def get_token(self, namespace: str, repo: str) -> Optional[str]:
"""
Get a GitHub token for requested repository.

Expand All @@ -20,7 +20,8 @@ def get_token(self, namespace: str, repo: str) -> str:
repo: Name of the repository.

Returns:
A token that can be used in PyGithub instance for authentication.
A token that can be used in PyGithub instance for authentication if it
can be obtained, None otherwise.
"""
raise NotImplementedError()

Expand Down
8 changes: 5 additions & 3 deletions ogr/services/github/auth_providers/github_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@


class GithubApp(GithubAuthentication):
def __init__(self, id: str, private_key: str, private_key_path: str) -> None:
def __init__(
self, id: str, private_key: Optional[str], private_key_path: Optional[str]
) -> None:
self.id = id
self._private_key = private_key
self._private_key_path = private_key_path
Expand Down Expand Up @@ -45,7 +47,7 @@ def __str__(self) -> str:
return f"GithubApp({censored_id}{censored_private_key}{private_key_path})"

@property
def private_key(self) -> str:
def private_key(self) -> Optional[str]:
if self._private_key:
return self._private_key

Expand All @@ -71,7 +73,7 @@ def integration(self) -> github.GithubIntegration:
self._integration = github.GithubIntegration(self.id, self.private_key)
return self._integration

def get_token(self, namespace: str, repo: str) -> str:
def get_token(self, namespace: str, repo: str) -> Optional[str]:
if not self.private_key:
return None

Expand Down
8 changes: 5 additions & 3 deletions ogr/services/github/auth_providers/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@


class TokenAuthentication(GithubAuthentication):
def __init__(self, token: str, max_retries: Union[int, Retry] = 0, **_) -> None:
def __init__(
self, token: Optional[str], max_retries: Union[int, Retry] = 0, **_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh no, memories of this magic are coming back /o\

) -> None:
self._token = token
self._pygithub_instance = github.Github(login_or_token=token, retry=max_retries)

Expand All @@ -29,11 +31,11 @@ def __str__(self) -> str:
def pygithub_instance(self) -> github.Github:
return self._pygithub_instance

def get_token(self, namespace: str, repo: str) -> str:
def get_token(self, namespace: str, repo: str) -> Optional[str]:
return self._token

@staticmethod
def try_create(
token: str = None, max_retries: Union[int, Retry] = 0, **_
token: Optional[str] = None, max_retries: Union[int, Retry] = 0, **_
) -> Optional["TokenAuthentication"]:
return TokenAuthentication(token, max_retries=max_retries)
11 changes: 8 additions & 3 deletions ogr/services/github/flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
# SPDX-License-Identifier: MIT

import datetime
from typing import List
from typing import List, Optional

from github import UnknownObjectException

from ogr.abstract import CommitFlag, CommitStatus
from ogr.services import github as ogr_github
from ogr.services.base import BaseCommitFlag
from ogr.exceptions import OgrException


class GithubCommitFlag(BaseCommitFlag):
Expand Down Expand Up @@ -65,8 +66,12 @@ def set(

@property
def created(self) -> datetime.datetime:
if not self._raw_commit_flag:
raise OgrException("Raw commit flag not set, this should not happen.")
return self._raw_commit_flag.created_at

@property
def edited(self) -> datetime.datetime:
return self._raw_commit_flag.updated_at
def edited(self) -> Optional[datetime.datetime]:
if self._raw_commit_flag:
return self._raw_commit_flag.updated_at
return None
2 changes: 1 addition & 1 deletion ogr/services/github/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def create(
body: str,
private: Optional[bool] = None,
labels: Optional[List[str]] = None,
assignees: Optional[list] = None,
assignees: Optional[List[str]] = None,
) -> "Issue":
if private:
raise OperationNotSupported("Private issues are not supported by Github")
Expand Down
25 changes: 13 additions & 12 deletions ogr/services/github/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
class GithubPullRequest(BasePullRequest):
_raw_pr: _GithubPullRequest
_target_project: "ogr_github.GithubProject"
_source_project: "ogr_github.GithubProject" = None
_source_project: Optional["ogr_github.GithubProject"] = None

@property
def title(self) -> str:
Expand Down Expand Up @@ -148,17 +148,18 @@ def create(
github_repo = project.github_repo

target_project = project
if project.is_fork and fork_username is None:
logger.warning(f"{project.full_repo_name} is fork, ignoring fork_repo.")
source_branch = f"{project.namespace}:{source_branch}"
github_repo = project.parent.github_repo
target_project = project.parent
elif fork_username:
source_branch = f"{fork_username}:{source_branch}"
if fork_username != project.namespace and project.parent is not None:
github_repo = GithubPullRequest.__get_fork(
fork_username, project.parent.github_repo
)
if project.parent:
if project.is_fork and fork_username is None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is exactly one thing that I don't like on static typing :D it has no clue about invariants and integrity

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, if it is a fork, then it definitely does have a parent but mypy doesn't know anything about it. I am not sure if we can somehow encode this relationship (I don't think so). One more check doesn't hurt and is safer... except that in some cases requre will shout :D

logger.warning(f"{project.full_repo_name} is fork, ignoring fork_repo.")
source_branch = f"{project.namespace}:{source_branch}"
github_repo = project.parent.github_repo
target_project = project.parent
elif fork_username:
source_branch = f"{fork_username}:{source_branch}"
if fork_username != project.namespace and project.parent is not None:
github_repo = GithubPullRequest.__get_fork(
fork_username, project.parent.github_repo
)

created_pr = github_repo.create_pull(
title=title, body=body, base=target_branch, head=source_branch
Expand Down
7 changes: 5 additions & 2 deletions ogr/services/github/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from github.GitRelease import GitRelease as PyGithubRelease

from ogr.abstract import Release, GitTag
from ogr.exceptions import GithubAPIException
from ogr.exceptions import GithubAPIException, OgrException
from ogr.services import github as ogr_github


Expand Down Expand Up @@ -45,7 +45,10 @@ def body(self):

@property
def git_tag(self) -> GitTag:
return self.project.get_tag_from_tag_name(self.tag_name)
tag = self.project.get_tag_from_tag_name(self.tag_name)
if not tag:
raise OgrException(f"No tag with name '{self.tag_name}' found.")
FrNecas marked this conversation as resolved.
Show resolved Hide resolved
return tag

@property
def tag_name(self) -> str:
Expand Down
4 changes: 3 additions & 1 deletion ogr/services/github/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ def project_create(
)

def get_pygithub_instance(self, namespace: str, repo: str) -> PyGithubInstance:
token = self.authentication.get_token(namespace, repo)
token = None
if self.authentication:
token = self.authentication.get_token(namespace, repo)
return PyGithubInstance(login_or_token=token, retry=self._max_retries)

def list_projects(
Expand Down
Loading