Pygithub: ๋ถ„๊ธฐ ๊ฐœ์ฒด์— ๋Œ€๋ถ€๋ถ„์˜ ์†์„ฑ๊ณผ ๋ฉ”์„œ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2018๋…„ 08์›” 17์ผ  ยท  11์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: PyGithub/PyGithub

์ €๋Š” Github Enterprise์—์„œ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ํฌ๋งํ•˜๋ฉด์„œ ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ง€๊ณ  ๋†€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ ์ค‘ ํ•˜๋‚˜๋Š” ์ €์žฅ์†Œ์— ๋Œ€ํ•œ ๋ถ„๊ธฐ ๋ณดํ˜ธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธ ์ €์žฅ์†Œ(์ด๋ฏธ ๋ถ„๊ธฐ ๋ณดํ˜ธ๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์Œ)์—์„œ ๋ถ„๊ธฐ ๊ฐœ์ฒด๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ํ• ๋‹น๋œ ์œ ์ผํ•œ ์†์„ฑ์€ "์ด๋ฆ„"์ด๋ฉฐ ์„ค๋ช…์„œ์— ๋‚˜์—ด๋œ ๋Œ€๋ถ€๋ถ„์˜ ๋ฉ”์„œ๋“œ๋Š” ๋ฌผ์ฒด:

import github
github.enable_console_debug_logging()
Github = github.Github

Github("myuser",
'mycreds',
base_url="https://ghe.workworkwork.com/api/v3")

master = g.get_user("myuser").get_repo('cookbook1').get_branch('master')
print master
print master.protection_url

์ถœ๋ ฅ:

...
GET https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/branches/master {'Authorization': 'Basic (login and password removed)', 'User-Agent': 'PyGithub/Python'} None ==> 200 {'status': '200 OK', 'content-length': '2925', 'x-github-media-type': 'github.v3', 'x-content-type-options': 'nosniff', 'content-security-policy': "default-src 'none'", 'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval', 'x-github-request-id': '0ec6f4e8-2355-41dc-859d-0e7076a294b1', 'strict-transport-security': 'max-age=31536000; includeSubdomains', 'vary': 'Accept, Authorization, Cookie, X-GitHub-OTP', 'server': 'GitHub.com', 'access-control-allow-origin': '*', 'etag': '"965449c848ed3319d7e028c2a587f414"', 'x-xss-protection': '1; mode=block', 'cache-control': 'private, max-age=60, s-maxage=60', 'date': 'Thu, 16 Aug 2018 23:41:19 GMT', 'x-frame-options': 'deny', 'x-oauth-scopes': 'admin:org_hook, admin:pre_receive_hook, admin:repo_hook, repo', 'content-type': 'application/json; charset=utf-8', 'x-accepted-oauth-scopes': ''} {"name":"master","commit":{"sha":"3548233b67c8ec4cad703ff947fa0ba022df7c66","commit":{"author":{"name":"Garrett Anderson","email":"[email protected]","date":"2018-08-16T18:44:44Z"},"committer":{"name":"GitHub Enterprise","email":"[email protected]","date":"2018-08-16T18:44:44Z"},"message":"Merge pull request #8 from myuser/newfork\n\nwhat","tree":{"sha":"44b3a300094b2ccbbfb79ea7df5f82e627c12b42","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/git/trees/44b3a300094b2ccbbfb79ea7df5f82e627c12b42"},"url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/git/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66","comment_count":0},"url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/3548233b67c8ec4cad703ff947fa0ba022df7c66","comments_url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3548233b67c8ec4cad703ff947fa0ba022df7c66/comments","author":{"login":"myuser","id":593,"avatar_url":"https://ghe.workworkwork.com/avatars/u/593?","gravatar_id":"","url":"https://ghe.workworkwork.com/api/v3/users/myuser","html_url":"https://ghe.workworkwork.com/myuser","followers_url":"https://ghe.workworkwork.com/api/v3/users/myuser/followers","following_url":"https://ghe.workworkwork.com/api/v3/users/myuser/following{/other_user}","gists_url":"https://ghe.workworkwork.com/api/v3/users/myuser/gists{/gist_id}","starred_url":"https://ghe.workworkwork.com/api/v3/users/myuser/starred{/owner}{/repo}","subscriptions_url":"https://ghe.workworkwork.com/api/v3/users/myuser/subscriptions","organizations_url":"https://ghe.workworkwork.com/api/v3/users/myuser/orgs","repos_url":"https://ghe.workworkwork.com/api/v3/users/myuser/repos","events_url":"https://ghe.workworkwork.com/api/v3/users/myuser/events{/privacy}","received_events_url":"https://ghe.workworkwork.com/api/v3/users/myuser/received_events","type":"User","site_admin":true},"committer":null,"parents":[{"sha":"067642113fe45ee77160ee28cafc4870f2c06b6a","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/067642113fe45ee77160ee28cafc4870f2c06b6a","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/067642113fe45ee77160ee28cafc4870f2c06b6a"},{"sha":"3a9479c605617a9035cd986a4bf77880ac5dde64","url":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/commits/3a9479c605617a9035cd986a4bf77880ac5dde64","html_url":"https://ghe.workworkwork.com/myuser/cookbook1/commit/3a9479c605617a9035cd986a4bf77880ac5dde64"}]},"_links":{"self":"https://ghe.workworkwork.com/api/v3/repos/myuser/cookbook1/branches/master","html":"https://ghe.workworkwork.com/myuser/cookbook1/tree/master"}}

Branch(name="master")

AttributeError: 'Branch' object has no attribute 'protection_url'

๊ฑฐ๊ธฐ์—๋Š” ์–ด๋–ค ๋ฐฉ๋ฒ•๋„ ์—†์Šต๋‹ˆ๋‹ค.

print master.get_required_status_checks()
OUTPUT:
AttributeError: 'Branch' object has no attribute 'get_required_status_checks'

๋˜๋Š” ์ด ์†์„ฑ:

print master.protection
OUTPUT:
return self._protected.value
AttributeError: 'Branch' object has no attribute '_protected'

๊ฐœ์ฒด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

print dir(master)
OUTPUT:
['CHECK_AFTER_INIT_FLAG', '_GithubObject__makeSimpleAttribute', '_GithubObject__makeSimpleListAttribute', '_GithubObject__makeTransformedAttribute', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_commit', '_completeIfNeeded', '_headers', '_initAttributes', '_makeBoolAttribute', '_makeClassAttribute', '_makeDatetimeAttribute', '_makeDictAttribute', '_makeDictOfStringsToClassesAttribute', '_makeIntAttribute', '_makeListOfClassesAttribute', '_makeListOfIntsAttribute', '_makeListOfListOfStringsAttribute', '_makeListOfStringsAttribute', '_makeStringAttribute', '_makeTimestampAttribute', '_name', '_parentUrl', '_rawData', '_requester', '_storeAndUseAttributes', '_useAttributes', 'commit', 'contexts', 'enforcement_level', 'etag', 'get__repr__', 'last_modified', 'name', 'protected', 'raw_data', 'raw_headers', 'setCheckAfterInitFlag']

๊ฐœ์ฒด๊ฐ€ ์ œ๋Œ€๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

bug stale

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋ถ„๊ธฐ ๋ณดํ˜ธ ์ฝ”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. -- Repository.get_branches()๊ฐ€ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ GitHub๋Š” Branch์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์œ ์šฉํ•  ๋งŒํผ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณดํ˜ธ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  11 ๋Œ“๊ธ€

์–ด๋–ค PyGithub ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? #790์€ ์ตœ๊ทผ์— ๋ณ‘ํ•ฉ๋˜์—ˆ์ง€๋งŒ ์•„์ง ๋ฆด๋ฆฌ์Šค๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์ €๋Š” PyGithub-1.40๊ณผ Github Enterprise 2.8.6์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
https://github.com/PyGithub/PyGithub/pull/790์€ ํ™•์‹คํžˆ ์œ ๋งํ•ด ๋ณด์ด์ง€๋งŒ ์šฐ๋ฆฌ ํšŒ์‚ฌ์˜ GHE ๋ฒ„์ „์ด ์•ฝ๊ฐ„ ์ตœ์‹  ๋ฒ„์ „์ด๋ฏ€๋กœ API ๋ณ€๊ฒฝ์ด ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—…๋ฐ์ดํŠธ: ์ตœ์‹  ๋งˆ์Šคํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ค‘

์ตœ์‹  ๋ณ‘ํ•ฉ์œผ๋กœ ๋ˆ„๋ฝ๋œ ์†์„ฑ ๋ฐ ๋ฉ”์„œ๋“œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋œ ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋‚ด API ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

master = g.get_user("ganderson").get_repo('cookbook1').get_branch('master')
print master.protection_url
master.get_protection()

์‚ฐ์ถœ:

None
Traceback (most recent call last):
  File "test_pygithub.py", line 12, in <module>
    master.get_protection()
  File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Branch.py", line 102, in get_protection
    self.protection_url
  File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Requester.py", line 260, in requestJsonAndCheck
    return self.__check(*self.requestJson(verb, url, parameters, headers, input, self.__customConnection(url)))
  File "/Users/me/tmp/ghe-python/venv/lib/python2.7/site-packages/github/Requester.py", line 276, in __customConnection
    if not url.startswith("/"):
AttributeError: 'NoneType' object has no attribute 'startswith'

์ด๊ฒƒ์€ ๋ณ„๊ฐœ์˜ ๋ฌธ์ œ์ธ ๊ฒƒ ๊ฐ™์œผ๋‹ˆ ์ด ํ‹ฐ์ผ“์„ ๋‹ซ๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์Šคํ„ฐ๊ฐ€ ๊ณ ์žฅ ๋‚˜๋ฉด ์ƒˆ๋กœ์šด ๋ฌธ์ œ๋ฅผ ์ œ๊ธฐํ•˜์‹ญ์‹œ์˜ค.

์ด๊ฒƒ์€ ๋˜ํ•œ ํ‘œ์ค€ api.github.com์˜ 1.43.2์—์„œ ๋‚˜์—๊ฒŒ ๊นจ์ง„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ณดํ˜ธ๋˜๋Š” 2๊ฐœ์˜ ๋ถ„๊ธฐ๊ฐ€ ์žˆ์ง€๋งŒ ๋ถ„๊ธฐ ๊ฐœ์ฒด๋Š” protection_url์„ ์„ค์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ณดํ˜ธ URL์ด ์„ค์ •๋˜์ง€ ์•Š๊ณ  URL์„ ๋นŒ๋“œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

[
  {
    "name": "develop",
    "commit": {
      "sha": "XXXX",
      "url": "https://api.github.com/repos/X/X/commits/X"
    }
  },
  {
    "name": "master",
    "commit": {
      "sha": "X",
      "url": "https://api.github.com/repos/X/X/commits/X"
    }
  }
]

branches?protected=true ์‚ฌ์šฉํ•˜์—ฌ ์ปฌํ•˜๋ฉด ํŽ˜์ด๋กœ๋“œ์— protection_url์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

[
  {
    "name": "develop",
    "commit": {
      "sha": "X",
      "url": "https://api.github.com/repos/X/X/commits/X"
    },
    "protected": true,
    "protection": {
      "enabled": true,
      "required_status_checks": {
        "enforcement_level": "off",
        "contexts": [

        ]
      }
    },
    "protection_url": "https://api.github.com/repos/X/X/branches/develop/protection"
  },
  {
    "name": "master",
    "commit": {
      "sha": "X",
      "url": "https://api.github.com/repos/X/X/commits/X"
    },
    "protected": true,
    "protection": {
      "enabled": true,
      "required_status_checks": {
        "enforcement_level": "off",
        "contexts": [

        ]
      }
    },
    "protection_url": "https://api.github.com/repos/X/X/branches/master/protection"
  }
]

๋งˆ์ง€๋ง‰ ๋Œ“๊ธ€ ์ž‘์„ฑ์ž๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ๋‹ค์‹œ ์—ฝ๋‹ˆ๋‹ค.

get_branch("master") ๋‹จ์ผ ์ง€์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ๋ฌธ์ œ๋Š” ํ˜„์žฌ get_branches ๋ฉ”์„œ๋“œ๊ฐ€ protected ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋Š” ๋ฐ ์•ฝ๊ฐ„์˜ ์‹œ๊ฐ„์„ ํ• ์• ํ•˜๊ณ  GitHub v3 API์— ๋Œ€ํ•ด ๋ฒ„๊ทธ๋ฅผ ์ œ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค.

https://github.com/dear-github/dear-github/issues/325

๋ถ„๊ธฐ ๋ณดํ˜ธ ์ฝ”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. -- Repository.get_branches()๊ฐ€ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ GitHub๋Š” Branch์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์œ ์šฉํ•  ๋งŒํผ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณดํ˜ธ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ถ„๊ธฐ ๋ณดํ˜ธ ์ฝ”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. -- Repository.get_branches()๊ฐ€ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ GitHub๋Š” Branch์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์œ ์šฉํ•  ๋งŒํผ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณดํ˜ธ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ ค๋ฉด Repository.get_branch('name')๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋Š˜ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ๋งŽ์€ ์‹œ๊ฐ„๊ณผ ์ขŒ์ ˆ์„ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค!

์ด ๋ฌธ์ œ๋Š” ์ตœ๊ทผ ํ™œ๋™์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™์œผ๋กœ ์˜ค๋ž˜๋œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋” ์ด์ƒ ํ™œ๋™์ด ์—†์œผ๋ฉด ํ์‡„๋ฉ๋‹ˆ๋‹ค. ๊ท€ํ•˜์˜ ๊ธฐ์—ฌ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰