github-local 0.0.1b3763__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- github_local-0.0.1b3763/.coveragerc +6 -0
- github_local-0.0.1b3763/.flake8 +3 -0
- github_local-0.0.1b3763/PKG-INFO +23 -0
- github_local-0.0.1b3763/README.md +5 -0
- github_local-0.0.1b3763/pyproject.toml +35 -0
- github_local-0.0.1b3763/pytest.ini +5 -0
- github_local-0.0.1b3763/python_packages/conftest.py +8 -0
- github_local-0.0.1b3763/python_packages/github_local/__init__.py +0 -0
- github_local-0.0.1b3763/python_packages/github_local/src/__init__.py +0 -0
- github_local-0.0.1b3763/python_packages/github_local/src/constants_github_local.py +6 -0
- github_local-0.0.1b3763/python_packages/github_local/src/github_local.py +91 -0
- github_local-0.0.1b3763/python_packages/github_local/tests/__init__.py +0 -0
- github_local-0.0.1b3763/python_packages/github_local/tests/unit/__init__.py +0 -0
- github_local-0.0.1b3763/python_packages/github_local/tests/unit/conftest.py +66 -0
- github_local-0.0.1b3763/python_packages/github_local/tests/unit/github_local_unit_test.py +118 -0
- github_local-0.0.1b3763/python_packages/github_local.egg-info/PKG-INFO +23 -0
- github_local-0.0.1b3763/python_packages/github_local.egg-info/SOURCES.txt +21 -0
- github_local-0.0.1b3763/python_packages/github_local.egg-info/dependency_links.txt +1 -0
- github_local-0.0.1b3763/python_packages/github_local.egg-info/requires.txt +2 -0
- github_local-0.0.1b3763/python_packages/github_local.egg-info/top_level.txt +1 -0
- github_local-0.0.1b3763/requirements.txt +3 -0
- github_local-0.0.1b3763/setup.cfg +4 -0
- github_local-0.0.1b3763/setup.py +95 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-local
|
|
3
|
+
Version: 0.0.1b3763
|
|
4
|
+
Summary: PyPI github-local Python Package owned by Circlez.ai
|
|
5
|
+
Home-page: https://github.com/circles-zone/github-local-python-package
|
|
6
|
+
Author: Circles
|
|
7
|
+
Author-email: info@circlez.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: python-sdk-remote
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-dist
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
PyPI github-local Python Package owned by Circlez.ai
|
|
23
|
+
GHA: https://github.com/circles-zone/github-local-python-package/actions
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# github-local
|
|
2
|
+
|
|
3
|
+
Base Python package for the GitHub REST API of the circles-zone organization: which Jira Work Items (by key, e.g. BU-1234) were merged to dev, and the branches of a Jira Work Item with the age of their last commit.
|
|
4
|
+
|
|
5
|
+
Environment variable: `GITHUB_TOKEN` (read access to the repos' contents and pull requests).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# This file should be in the future instead of setup.py
|
|
2
|
+
# https://python-poetry.org/docs/pyproject
|
|
3
|
+
# https://stackoverflow.com/questions/78048223/adding-folder-with-data-with-pyproject-toml
|
|
4
|
+
# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license
|
|
5
|
+
|
|
6
|
+
# This file is mandatory for the `poetry version patch`
|
|
7
|
+
|
|
8
|
+
# It seems we need to copy this file also to serverless-com repo, as required by dialog-workflow-python-package
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
# "setuptools_scm>=6.2" is required for writing the version in my_package/_version.py and may be also for "poetry version patch" to work
|
|
12
|
+
requires = ["setuptools>=61.0", "setuptools_scm>=6.2"]
|
|
13
|
+
build-backend = "setuptools.build_meta"
|
|
14
|
+
|
|
15
|
+
[tool.setuptools_scm]
|
|
16
|
+
write_to = "python_packages/github_local/_version.py"
|
|
17
|
+
|
|
18
|
+
[tool.pytest.ini_options]
|
|
19
|
+
pythonpath = ["."]
|
|
20
|
+
|
|
21
|
+
[tool.poetry]
|
|
22
|
+
# TODO: Please update the name, similar to storage-local (suffix -local)
|
|
23
|
+
name = "github-local"
|
|
24
|
+
# TODO After the b (beta), you should add the Jira Work Item ID (was generated by Jira). If you run sdk-local Makec.ps1 it should do it for you.
|
|
25
|
+
# I believe we are still using the version from setup.py and not from here until Potery will work
|
|
26
|
+
version = "0.0.1b3763" # https://pypi.org/project/github-local/
|
|
27
|
+
description = "github-local Python Package"
|
|
28
|
+
readme = "README.md"
|
|
29
|
+
authors = [
|
|
30
|
+
"Circlez.ai <info@circlez.ai>",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.poetry.dev-dependencies]
|
|
34
|
+
pytest = "^8.0"
|
|
35
|
+
pytest-cov = "^5.0"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
from python_sdk_remote.utilities import our_get_env, our_set_env
|
|
3
|
+
|
|
4
|
+
load_dotenv()
|
|
5
|
+
|
|
6
|
+
for key, default_value in {"BRAND_NAME": "Circlez", "ENVIRONMENT_NAME": "play1"}.items():
|
|
7
|
+
if not our_get_env(key, raise_if_not_found=False):
|
|
8
|
+
our_set_env(key, default_value)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
GITHUB_TOKEN_ENV = "GITHUB_TOKEN"
|
|
2
|
+
GITHUB_ORGANIZATION = "circles-zone"
|
|
3
|
+
GITHUB_API_URL = "https://api.github.com"
|
|
4
|
+
GITHUB_MAX_KEYS_PER_SEARCH = 5 # GitHub search allows up to 5 OR operators
|
|
5
|
+
GITHUB_SEARCH_MAX_PAGES = 10 # GitHub search returns at most 1000 results (10 pages of 100)
|
|
6
|
+
GITHUB_REQUEST_TIMEOUT_SECONDS = 10
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from python_sdk_remote.utilities import our_get_env
|
|
7
|
+
|
|
8
|
+
from .constants_github_local import (
|
|
9
|
+
GITHUB_API_URL,
|
|
10
|
+
GITHUB_MAX_KEYS_PER_SEARCH,
|
|
11
|
+
GITHUB_ORGANIZATION,
|
|
12
|
+
GITHUB_REQUEST_TIMEOUT_SECONDS,
|
|
13
|
+
GITHUB_SEARCH_MAX_PAGES,
|
|
14
|
+
GITHUB_TOKEN_ENV,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
WORK_ITEM_KEY_PATTERN = re.compile(r"\b[A-Z][A-Z0-9]+-\d+\b")
|
|
18
|
+
GITHUB_PAGE_SIZE = 100
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GitHubLocalException(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_branch_of_key(branch_name: str, key: str) -> bool:
|
|
26
|
+
"""feature/BU-1-x and BU-1 belong to BU-1, feature/BU-10-x does not."""
|
|
27
|
+
is_branch_of_key_result = re.fullmatch(rf"(feature/)?{re.escape(key)}([^0-9].*)?", branch_name,
|
|
28
|
+
re.IGNORECASE) is not None
|
|
29
|
+
return is_branch_of_key_result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GitHubLocal:
|
|
33
|
+
"""Which Jira Work Items were merged to dev, and the branches of a Jira Work Item."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, token: Optional[str] = None, organization: str = GITHUB_ORGANIZATION) -> None:
|
|
36
|
+
self.organization = organization
|
|
37
|
+
github_token = token or our_get_env(GITHUB_TOKEN_ENV, raise_if_empty=True)
|
|
38
|
+
self.session = requests.Session()
|
|
39
|
+
self.session.headers.update({"Accept": "application/vnd.github+json",
|
|
40
|
+
"Authorization": f"Bearer {github_token}"})
|
|
41
|
+
|
|
42
|
+
def get_keys_merged_to_dev(self, keys: list[str]) -> set[str]:
|
|
43
|
+
"""PR title = branch name = feature/BU-1234-..., so a merged PR to dev with the key in its title
|
|
44
|
+
means the Jira Work Item was merged."""
|
|
45
|
+
merged_keys: set[str] = set()
|
|
46
|
+
for start in range(0, len(keys), GITHUB_MAX_KEYS_PER_SEARCH):
|
|
47
|
+
batch = keys[start:start + GITHUB_MAX_KEYS_PER_SEARCH]
|
|
48
|
+
query = f"org:{self.organization} is:pr is:merged base:dev in:title " + " OR ".join(batch)
|
|
49
|
+
for page in range(1, GITHUB_SEARCH_MAX_PAGES + 1):
|
|
50
|
+
search_result = self._get("/search/issues", params={"q": query, "per_page": GITHUB_PAGE_SIZE,
|
|
51
|
+
"page": page})
|
|
52
|
+
pull_requests = search_result.get("items") or []
|
|
53
|
+
for pull_request in pull_requests:
|
|
54
|
+
merged_keys.update(key for key in WORK_ITEM_KEY_PATTERN.findall(
|
|
55
|
+
str(pull_request.get("title", "")).upper()) if key in batch)
|
|
56
|
+
if len(pull_requests) < GITHUB_PAGE_SIZE:
|
|
57
|
+
break
|
|
58
|
+
return merged_keys
|
|
59
|
+
|
|
60
|
+
def get_branches(self, repo_name: str, key: str) -> list[dict[str, Any]]:
|
|
61
|
+
"""Branches named feature/<key>... or <key>... (exactly this key) with the age of their last commit."""
|
|
62
|
+
branches = []
|
|
63
|
+
for prefix in (f"feature/{key}", key):
|
|
64
|
+
refs = self._get(f"/repos/{self.organization}/{repo_name}/git/matching-refs/heads/{prefix}",
|
|
65
|
+
is_not_found_empty=True)
|
|
66
|
+
for ref in refs:
|
|
67
|
+
branch_name = ref["ref"].removeprefix("refs/heads/")
|
|
68
|
+
if not is_branch_of_key(branch_name, key):
|
|
69
|
+
continue
|
|
70
|
+
commit = self._get(f"/repos/{self.organization}/{repo_name}/commits/{ref['object']['sha']}")
|
|
71
|
+
last_commit_time = datetime.fromisoformat(commit["commit"]["committer"]["date"].replace("Z", "+00:00"))
|
|
72
|
+
branches.append({"repo_name": repo_name, "branch_name": branch_name,
|
|
73
|
+
"age": datetime.now(timezone.utc) - last_commit_time})
|
|
74
|
+
return branches
|
|
75
|
+
|
|
76
|
+
def _get(self, path: str, params: Optional[dict[str, Any]] = None, is_not_found_empty: bool = False) -> Any:
|
|
77
|
+
try:
|
|
78
|
+
response = self.session.get(f"{GITHUB_API_URL}{path}", params=params,
|
|
79
|
+
timeout=GITHUB_REQUEST_TIMEOUT_SECONDS)
|
|
80
|
+
except requests.RequestException as exception:
|
|
81
|
+
raise GitHubLocalException(f"GitHub is unreachable: {exception}")
|
|
82
|
+
if response.status_code == 404 and is_not_found_empty:
|
|
83
|
+
response_json: Any = []
|
|
84
|
+
elif not response.ok:
|
|
85
|
+
raise GitHubLocalException(f"GitHub GET {path} failed with {response.status_code}: {response.text[:200]}")
|
|
86
|
+
else:
|
|
87
|
+
try:
|
|
88
|
+
response_json = response.json()
|
|
89
|
+
except ValueError:
|
|
90
|
+
raise GitHubLocalException(f"GitHub GET {path} returned an invalid response: {response.text[:200]}")
|
|
91
|
+
return response_json
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import json as json_module
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Optional, Union
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FakeHttpCall:
|
|
11
|
+
method: str
|
|
12
|
+
url: str
|
|
13
|
+
params: Optional[dict[str, Any]]
|
|
14
|
+
json: Optional[dict[str, Any]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FakeHttp:
|
|
18
|
+
"""Answers requests.Session.request with the responses registered per method and URL, in order
|
|
19
|
+
(the last one repeats), and records every call. A request without a registered response fails the test."""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.calls: list[FakeHttpCall] = []
|
|
23
|
+
self.responses_by_request: dict[tuple[str, str], list[Union[requests.Response, Exception]]] = {}
|
|
24
|
+
|
|
25
|
+
def add(self, method: str, url: str, status: int = 200, json: Any = None,
|
|
26
|
+
body: Union[str, Exception] = "") -> None:
|
|
27
|
+
if isinstance(body, Exception):
|
|
28
|
+
response: Union[requests.Response, Exception] = body
|
|
29
|
+
else:
|
|
30
|
+
response = requests.Response()
|
|
31
|
+
response.status_code = status
|
|
32
|
+
response.url = url
|
|
33
|
+
response._content = (body if json is None else json_module.dumps(json)).encode()
|
|
34
|
+
self.responses_by_request.setdefault((method, url), []).append(response)
|
|
35
|
+
|
|
36
|
+
def get(self, url: str, **kwargs: Any) -> None:
|
|
37
|
+
self.add("GET", url, **kwargs)
|
|
38
|
+
|
|
39
|
+
def post(self, url: str, **kwargs: Any) -> None:
|
|
40
|
+
self.add("POST", url, **kwargs)
|
|
41
|
+
|
|
42
|
+
def delete(self, url: str, **kwargs: Any) -> None:
|
|
43
|
+
self.add("DELETE", url, **kwargs)
|
|
44
|
+
|
|
45
|
+
def request(self, method: str, url: str, params: Optional[dict[str, Any]] = None,
|
|
46
|
+
json: Optional[dict[str, Any]] = None, **kwargs: Any) -> requests.Response:
|
|
47
|
+
self.calls.append(FakeHttpCall(method=method, url=url, params=params, json=json))
|
|
48
|
+
registered_responses = self.responses_by_request.get((method, url))
|
|
49
|
+
assert registered_responses, f"Unexpected HTTP request {method} {url}"
|
|
50
|
+
response = registered_responses.pop(0) if len(registered_responses) > 1 else registered_responses[0]
|
|
51
|
+
if isinstance(response, Exception):
|
|
52
|
+
raise response
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(autouse=True)
|
|
57
|
+
def fake_http(monkeypatch) -> FakeHttp:
|
|
58
|
+
"""Unit tests never reach GitHub: every requests.Session request is answered by FakeHttp."""
|
|
59
|
+
fake_http = FakeHttp()
|
|
60
|
+
|
|
61
|
+
def session_request(session: requests.Session, method: str, url: str, **kwargs: Any) -> requests.Response:
|
|
62
|
+
response = fake_http.request(method, url, **kwargs)
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
monkeypatch.setattr(requests.Session, "request", session_request)
|
|
66
|
+
return fake_http
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import requests
|
|
5
|
+
from python_sdk_remote.utilities import our_delete_env, our_set_env
|
|
6
|
+
|
|
7
|
+
from github_local.src.github_local import GitHubLocal, GitHubLocalException, is_branch_of_key
|
|
8
|
+
|
|
9
|
+
API = "https://api.github.com"
|
|
10
|
+
REPO = "jira-local-python-package"
|
|
11
|
+
REFS = f"{API}/repos/circles-zone/{REPO}/git/matching-refs/heads"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def github_local() -> GitHubLocal:
|
|
16
|
+
github_local = GitHubLocal(token="github-token")
|
|
17
|
+
return github_local
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def commit_time(age: timedelta) -> str:
|
|
21
|
+
commit_time_result = (datetime.now(timezone.utc) - age).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
22
|
+
return commit_time_result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_get_keys_merged_to_dev_searches_in_batches_of_5(fake_http, github_local):
|
|
26
|
+
fake_http.get(f"{API}/search/issues", json={"items": [
|
|
27
|
+
{"title": "feature/BU-1-a-Yuval-Rotich"}, {"title": "feature/bu-3-b (also BU-99 not asked)"}, {}]})
|
|
28
|
+
fake_http.get(f"{API}/search/issues", json={"items": []})
|
|
29
|
+
|
|
30
|
+
merged_keys = github_local.get_keys_merged_to_dev([f"BU-{number}" for number in range(1, 7)])
|
|
31
|
+
|
|
32
|
+
assert merged_keys == {"BU-1", "BU-3"}
|
|
33
|
+
assert fake_http.calls[0].params["q"] == ("org:circles-zone is:pr is:merged base:dev in:title "
|
|
34
|
+
"BU-1 OR BU-2 OR BU-3 OR BU-4 OR BU-5")
|
|
35
|
+
assert fake_http.calls[1].params["q"].endswith("in:title BU-6")
|
|
36
|
+
assert github_local.session.headers["Authorization"] == "Bearer github-token"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_get_keys_merged_to_dev_reads_all_the_pages(fake_http, github_local):
|
|
40
|
+
fake_http.get(f"{API}/search/issues", json={"items": [{"title": "feature/BU-9-x"}] * 99 + [{"title": "x"}]})
|
|
41
|
+
fake_http.get(f"{API}/search/issues", json={"items": [{"title": "feature/BU-1-x"}]})
|
|
42
|
+
|
|
43
|
+
assert github_local.get_keys_merged_to_dev(["BU-1", "BU-9"]) == {"BU-1", "BU-9"}
|
|
44
|
+
assert [call.params["page"] for call in fake_http.calls] == [1, 2]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_get_keys_merged_to_dev_stops_at_the_github_search_limit(fake_http, github_local):
|
|
48
|
+
fake_http.get(f"{API}/search/issues", json={"items": [{"title": "x"}] * 100})
|
|
49
|
+
|
|
50
|
+
github_local.get_keys_merged_to_dev(["BU-1"])
|
|
51
|
+
|
|
52
|
+
assert len(fake_http.calls) == 10
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_get_keys_merged_to_dev_without_keys_calls_github_zero_times(fake_http, github_local):
|
|
56
|
+
assert github_local.get_keys_merged_to_dev([]) == set()
|
|
57
|
+
assert len(fake_http.calls) == 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_search_not_found_is_an_error_and_not_an_empty_list(fake_http, github_local):
|
|
61
|
+
fake_http.get(f"{API}/search/issues", status=404, json={"message": "Not Found"})
|
|
62
|
+
|
|
63
|
+
with pytest.raises(GitHubLocalException, match="404"):
|
|
64
|
+
github_local.get_keys_merged_to_dev(["BU-1"])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_get_branches_of_exactly_this_key_with_their_age(fake_http, github_local):
|
|
68
|
+
fake_http.get(f"{REFS}/feature/BU-1", json=[
|
|
69
|
+
{"ref": "refs/heads/feature/BU-1-a-Yuval-Rotich", "object": {"sha": "abc"}},
|
|
70
|
+
{"ref": "refs/heads/feature/BU-10-other", "object": {"sha": "def"}}])
|
|
71
|
+
fake_http.get(f"{REFS}/BU-1", status=404)
|
|
72
|
+
fake_http.get(f"{API}/repos/circles-zone/{REPO}/commits/abc",
|
|
73
|
+
json={"commit": {"committer": {"date": commit_time(timedelta(days=3))}}})
|
|
74
|
+
|
|
75
|
+
branches = github_local.get_branches(repo_name=REPO, key="BU-1")
|
|
76
|
+
|
|
77
|
+
assert [(branch["repo_name"], branch["branch_name"]) for branch in branches] == \
|
|
78
|
+
[(REPO, "feature/BU-1-a-Yuval-Rotich")]
|
|
79
|
+
assert timedelta(days=3) - timedelta(minutes=1) < branches[0]["age"] < timedelta(days=3, minutes=1)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize("branch_name, is_of_key", [
|
|
83
|
+
("feature/BU-1-a", True), ("feature/BU-1", True), ("BU-1_old", True), ("bu-1-lower", True),
|
|
84
|
+
("feature/BU-10-a", False), ("BU-12", False), ("feature/other-BU-1", False)])
|
|
85
|
+
def test_is_branch_of_key(branch_name, is_of_key):
|
|
86
|
+
assert is_branch_of_key(branch_name, "BU-1") == is_of_key
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parametrize("status", [403, 429, 500])
|
|
90
|
+
def test_github_errors_raise(fake_http, github_local, status):
|
|
91
|
+
fake_http.get(f"{API}/search/issues", status=status, body="error")
|
|
92
|
+
|
|
93
|
+
with pytest.raises(GitHubLocalException, match=str(status)):
|
|
94
|
+
github_local.get_keys_merged_to_dev(["BU-1"])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_github_invalid_json_raises(fake_http, github_local):
|
|
98
|
+
fake_http.get(f"{API}/search/issues", body="<html>not json</html>")
|
|
99
|
+
|
|
100
|
+
with pytest.raises(GitHubLocalException, match="invalid response"):
|
|
101
|
+
github_local.get_keys_merged_to_dev(["BU-1"])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_github_unreachable_raises(fake_http, github_local):
|
|
105
|
+
fake_http.get(f"{API}/search/issues", body=requests.ConnectionError("refused"))
|
|
106
|
+
|
|
107
|
+
with pytest.raises(GitHubLocalException, match="unreachable"):
|
|
108
|
+
github_local.get_keys_merged_to_dev(["BU-1"])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_token_is_read_from_environment_variable():
|
|
112
|
+
our_set_env("GITHUB_TOKEN", "env-token")
|
|
113
|
+
try:
|
|
114
|
+
github_local = GitHubLocal()
|
|
115
|
+
finally:
|
|
116
|
+
our_delete_env("GITHUB_TOKEN")
|
|
117
|
+
|
|
118
|
+
assert github_local.session.headers["Authorization"] == "Bearer env-token"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-local
|
|
3
|
+
Version: 0.0.1b3763
|
|
4
|
+
Summary: PyPI github-local Python Package owned by Circlez.ai
|
|
5
|
+
Home-page: https://github.com/circles-zone/github-local-python-package
|
|
6
|
+
Author: Circles
|
|
7
|
+
Author-email: info@circlez.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: python-sdk-remote
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-dist
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
PyPI github-local Python Package owned by Circlez.ai
|
|
23
|
+
GHA: https://github.com/circles-zone/github-local-python-package/actions
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.coveragerc
|
|
2
|
+
.flake8
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
pytest.ini
|
|
6
|
+
requirements.txt
|
|
7
|
+
setup.py
|
|
8
|
+
python_packages/conftest.py
|
|
9
|
+
python_packages/github_local/__init__.py
|
|
10
|
+
python_packages/github_local.egg-info/PKG-INFO
|
|
11
|
+
python_packages/github_local.egg-info/SOURCES.txt
|
|
12
|
+
python_packages/github_local.egg-info/dependency_links.txt
|
|
13
|
+
python_packages/github_local.egg-info/requires.txt
|
|
14
|
+
python_packages/github_local.egg-info/top_level.txt
|
|
15
|
+
python_packages/github_local/src/__init__.py
|
|
16
|
+
python_packages/github_local/src/constants_github_local.py
|
|
17
|
+
python_packages/github_local/src/github_local.py
|
|
18
|
+
python_packages/github_local/tests/__init__.py
|
|
19
|
+
python_packages/github_local/tests/unit/__init__.py
|
|
20
|
+
python_packages/github_local/tests/unit/conftest.py
|
|
21
|
+
python_packages/github_local/tests/unit/github_local_unit_test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github_local
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# serverless.com repo "python -m build" works without setup.py
|
|
2
|
+
# Should update package_dir if not using our recommended directory structure
|
|
3
|
+
|
|
4
|
+
import setuptools
|
|
5
|
+
|
|
6
|
+
# For reading the version my_package/_version.py or my_package/__init__.py
|
|
7
|
+
|
|
8
|
+
import codecs
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read(rel_path):
|
|
13
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
14
|
+
with codecs.open(os.path.join(here, rel_path), 'r') as fp:
|
|
15
|
+
content = fp.read()
|
|
16
|
+
return content
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_version(rel_path):
|
|
20
|
+
for line in read(rel_path).splitlines():
|
|
21
|
+
if line.startswith('__version__'):
|
|
22
|
+
version = line.split('"')[1] # Or "'", depending on the quotes used
|
|
23
|
+
return version
|
|
24
|
+
raise RuntimeError("Unable to find version string.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# TODO: Change the PACKAGE_NAME to the package's name - Either xxx-local or
|
|
28
|
+
# xxx-remote (without the -python-package suffix). Only lowercase, no
|
|
29
|
+
# underlines.
|
|
30
|
+
# Used by pypa/gh-action-pypi-publish
|
|
31
|
+
# Package Name should be identical to the inner directory name
|
|
32
|
+
# Changing the package name here, will cause a change in the package directory
|
|
33
|
+
# name as well
|
|
34
|
+
# PACKAGE_NAME should be singular if handling only one instance
|
|
35
|
+
# PACKAGE_NAME should not include the word "main"
|
|
36
|
+
|
|
37
|
+
# e.g.: queue-local, without python-package suffix
|
|
38
|
+
PACKAGE_NAME = "github-local"
|
|
39
|
+
NEW_LINE = "\n"
|
|
40
|
+
|
|
41
|
+
package_dir = PACKAGE_NAME.replace("-", "_")
|
|
42
|
+
# If we need backward-compatible:
|
|
43
|
+
# old_package_dir = "old_package_name"
|
|
44
|
+
|
|
45
|
+
PACKAGE_DESCRIPTION = f"PyPI {PACKAGE_NAME} Python Package owned by Circlez.ai"
|
|
46
|
+
|
|
47
|
+
setuptools.setup(
|
|
48
|
+
name=PACKAGE_NAME,
|
|
49
|
+
# Increase this number every time you make a change
|
|
50
|
+
# want to publish. After 0.0.9 switch to 0.0.10 and not 0.1.0
|
|
51
|
+
# version can't be 0.0.
|
|
52
|
+
# If you find yourself increasing the version in more than one patch number (it might indicate there are multiple branches, which you should submit their PRs before your PR) # noqa: E501
|
|
53
|
+
# Updating the b{{JiraWorkItemId}} done automatically by makec.ps1 / make_py.ps1
|
|
54
|
+
# TODO add the Jira Work Item (Jira Issue) number after the version, i.e., 0.0.1b2432
|
|
55
|
+
# TODO: According to GitFlow, in the Feature Branch, please update after the letter 'b' the Jira Work Item ID, and in the exceptional case you can't get a Jira Work Item directly or using the Slack Channel, please use the PR number. Semantic Version simver.org
|
|
56
|
+
version='0.0.1b3763', # https://pypi.org/project/github-local#History
|
|
57
|
+
# TODO version=get_version("{{PackageDirectorySnake}}/__init__.py"),
|
|
58
|
+
author="Circles",
|
|
59
|
+
author_email="info@circlez.ai",
|
|
60
|
+
description=f"{PACKAGE_DESCRIPTION}",
|
|
61
|
+
# long_description=f"{description}",
|
|
62
|
+
long_description =f"{PACKAGE_DESCRIPTION}" + NEW_LINE # noqa E501
|
|
63
|
+
+ "GHA: https://github.com/circles-zone/github-local-python-package/actions", # noqa E501
|
|
64
|
+
|
|
65
|
+
long_description_content_type='text/markdown',
|
|
66
|
+
url=f"https://github.com/circles-zone/{PACKAGE_NAME}-python-package",
|
|
67
|
+
# packages=[package_dir],
|
|
68
|
+
# packages=[package_dir, old_package_dir],
|
|
69
|
+
packages=setuptools.find_packages(where="python_packages", exclude=["*.tests", "*.tests.*"]),
|
|
70
|
+
package_dir={"": "python_packages"},
|
|
71
|
+
|
|
72
|
+
# I'm not sure we need package_dir
|
|
73
|
+
# package_dir={package_dir: f'{package_dir}/src'},
|
|
74
|
+
# TODO Unfortunately in event-main-local-restapi there are no
|
|
75
|
+
# repo-directory and no package directory (flat directory structure)
|
|
76
|
+
# package_dir={package_dir: f'src'},
|
|
77
|
+
|
|
78
|
+
# package_dir={package_dir: f'{package_dir}/src',
|
|
79
|
+
# old_package_dir: f'{package_dir}/src'},
|
|
80
|
+
package_data={package_dir: ['*.py']},
|
|
81
|
+
classifiers=[
|
|
82
|
+
"Programming Language :: Python :: 3",
|
|
83
|
+
# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license
|
|
84
|
+
# TODO Fix all repos, especially the Python templates
|
|
85
|
+
# "License :: MIT AND (Apache-2.0 OR BSD-2-Clause)",
|
|
86
|
+
"Operating System :: OS Independent",
|
|
87
|
+
],
|
|
88
|
+
# Do not add packages needed only for tests, i.e., packages for get_test_xxx(
|
|
89
|
+
# TODO: Update which packages to include with this package in production
|
|
90
|
+
# (dependencies) - Not for development/testing
|
|
91
|
+
install_requires=[
|
|
92
|
+
'python-sdk-remote',
|
|
93
|
+
'requests',
|
|
94
|
+
]
|
|
95
|
+
)
|