Skip to content
Merged
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
26 changes: 25 additions & 1 deletion .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,41 @@ jobs:
python-version: '3.11'

- uses: astral-sh/ruff-action@v3
with:
version: "latest"

- name: Ruff lint
run: ruff check .

- name: Ruff fmt
run: ruff format .

test:
name: Python tests
runs-on: ubuntu-latest
needs: lint

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v6

- name: uv sync
run: uv sync

- uses: extractions/setup-just@v3

- name: Run tests
run: |
source .venv/bin/activate
just test

build:
name: Build
runs-on: ubuntu-latest
needs: lint
needs: [lint, test]

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ cython_debug/
.secrets.*

# VScode directories
.vscode
# .vscode

# Shell script files
*.sh
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": true
}
9 changes: 6 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Justfile for Python
# justfile for python

set dotenv-load := true

Expand Down Expand Up @@ -28,6 +28,9 @@ alias dv := dynaconf-validate
@lint:
ruff check .

@test:
pytest -v

@fmt:
ruff format .

Expand All @@ -37,5 +40,5 @@ alias dv := dynaconf-validate
@dynaconf-validate:
dynaconf -i github_rest_cli.config.settings validate

test:
@echo {{ justfile_directory() }}
@profile *args:
pyinstrument --no-color -m github_rest_cli.main {{ args }}
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"dynaconf>=3.2.11",
]
readme = "README.md"
requires-python = ">= 3.11"
requires-python = ">= 3.11.5"

[project.scripts]
github-rest-cli = 'github_rest_cli.main:cli'
Expand All @@ -22,7 +22,11 @@ build-backend = "hatchling.build"

[tool.uv]
managed = true
dev-dependencies = []
dev-dependencies = [
"pyinstrument>=5.0.2",
"pytest>=8.4.0",
"pytest-mock>=3.14.1",
]

[tool.hatch.metadata]
allow-direct-references = true
Expand Down
39 changes: 18 additions & 21 deletions src/github_rest_cli/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ def fetch_user() -> str:


def get_repository(name: str, org: str = None):
owner = fetch_user()
owner = org if org else fetch_user()
headers = get_headers()
url = build_url("repos", org or owner, name)
url = build_url("repos", owner, name)

response = request_with_handling(
"GET",
url,
Expand All @@ -71,28 +72,28 @@ def get_repository(name: str, org: str = None):


def create_repository(name: str, visibility: str, org: str = None, empty: bool = False):
data = {
payload = {
"name": name,
"visibility": visibility,
"auto_init": True,
}

if visibility == "private":
data["private"] = True
payload["private"] = True

if empty:
data["auto_init"] = False
payload["auto_init"] = False

owner = fetch_user()
owner = org if org else fetch_user()
headers = get_headers()
url = build_url("orgs", org, "repos") if org else build_url("user", "repos")
url = build_url("orgs", owner, "repos") if org else build_url("user", "repos")

return request_with_handling(
"POST",
url,
headers=headers,
json=data,
success_msg=f"Repository successfully created in {org or owner}/{name}.",
json=payload,
success_msg=f"Repository successfully created in {owner}/{name}.",
error_msg={
401: "Unauthorized access. Please check your token or credentials.",
422: "Repository name already exists on this account or organization.",
Expand All @@ -101,15 +102,15 @@ def create_repository(name: str, visibility: str, org: str = None, empty: bool =


def delete_repository(name: str, org: str = None):
owner = fetch_user()
owner = org if org else fetch_user()
headers = get_headers()
url = build_url("repos", org, name) if org else build_url("repos", owner, name)
url = build_url("repos", owner, name)

return request_with_handling(
"DELETE",
url,
headers=headers,
success_msg=f"Repository sucessfully deleted in {org or owner}/{name}.",
success_msg=f"Repository sucessfully deleted in {owner}/{name}.",
error_msg={
403: "The authenticated user does not have sufficient permissions to delete this repository.",
404: "The requested repository does not exist.",
Expand Down Expand Up @@ -142,9 +143,9 @@ def list_repositories(page: int, property: str, role: str):
def dependabot_security(name: str, enabled: bool, org: str = None):
is_enabled = bool(enabled)

owner = fetch_user()
owner = org if org else fetch_user()
headers = get_headers()
url = build_url("repos", org, name) if org else build_url("repos", owner, name)
url = build_url("repos", owner, name)
security_urls = ["vulnerability-alerts", "automated-security-fixes"]

if is_enabled:
Expand All @@ -165,19 +166,15 @@ def dependabot_security(name: str, enabled: bool, org: str = None):
"DELETE",
url=full_url,
headers=headers,
success_msg=f"Dependabot has been disabled on repository {org or owner}/{name}.",
success_msg=f"Dependabot has been disabled on repository {owner}/{name}.",
error_msg={401: "Unauthorized. Please check your credentials."},
)


def deployment_environment(name: str, env: str, org: str = None):
owner = fetch_user()
owner = org if org else fetch_user()
headers = get_headers()
url = (
build_url("repos", org, name, "environments", env)
if org
else build_url("repos", owner, name, "environments", env)
)
url = build_url("repos", owner, name, "environments", env)

return request_with_handling(
"PUT",
Expand Down
44 changes: 44 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from github_rest_cli import api


GET_HEADERS_FUNCTION = "github_rest_cli.api.get_headers"
FETCH_USER_FUNCTION = "github_rest_cli.api.fetch_user"
REQUEST_HANDLER_FUNCTION = "github_rest_cli.api.request_with_handling"


def test_fetch_user(mocker):
mocker.patch(GET_HEADERS_FUNCTION, return_value={"Authorization": "token fake"})

mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"login": "test-user"}
mock_response.raise_for_status = lambda: None

mocker.patch(REQUEST_HANDLER_FUNCTION, return_value=mock_response)

result = api.fetch_user()

assert result == "test-user"


def test_create_repository_user(mocker):
expected_message = "Repository successfully created in test-user/test-repo."

mocker.patch(GET_HEADERS_FUNCTION, return_value={"Authorization": "token fake"})
mocker.patch(FETCH_USER_FUNCTION, return_value="test-user")
mocker.patch(REQUEST_HANDLER_FUNCTION, return_value=expected_message)

result = api.create_repository("test-repo", "public")

assert result == expected_message


def test_create_repository_org(mocker):
expected_message = "Repository successfully created in test-org/test-repo."

mocker.patch(GET_HEADERS_FUNCTION, return_value={"Authorization": "token fake"})
mocker.patch(REQUEST_HANDLER_FUNCTION, return_value=expected_message)

result = api.create_repository("test-repo", "public", "test-org")

assert result == expected_message
Loading