Skip to content

Commit f854318

Browse files
committed
Update licenses module to better represent differences
This splits up the different representations of licenses across the GitHub API for acccuracy.
1 parent 973435c commit f854318

File tree

11 files changed

+232
-60
lines changed

11 files changed

+232
-60
lines changed

github3/github.py

+15-11
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
from .search import (CodeSearchResult, IssueSearchResult,
2525
RepositorySearchResult, UserSearchResult)
2626
from .structs import SearchIterator
27+
from . import licenses
2728
from . import notifications
2829
from . import orgs
2930
from . import users
30-
from .licenses import License
3131
from uritemplate import URITemplate
3232

3333

@@ -738,22 +738,27 @@ def keys(self, number=-1, etag=None):
738738
def license(self, name):
739739
"""Retrieve the license specified by the name.
740740
741-
:param string name: (required), name of license
742-
:returns: :class:`License <github3.licenses.License>`
741+
:param string name:
742+
(required), name of license
743+
:returns:
744+
the specified license
745+
:rtype:
746+
:class:`~github3.licenses.License`
743747
"""
744-
745748
url = self._build_url('licenses', name)
746-
json = self._json(self._get(url, headers=License.CUSTOM_HEADERS), 200)
747-
return self._instance_or_null(License, json)
749+
json = self._json(self._get(url), 200)
750+
return self._instance_or_null(licenses.License, json)
748751

749752
def licenses(self, number=-1, etag=None):
750753
"""Iterate over open source licenses.
751754
752-
:returns: generator of :class:`License <github3.licenses.License>`
755+
:returns:
756+
generator of short license objects
757+
:rtype:
758+
:class:`~github3.licenses.ShortLicense`
753759
"""
754760
url = self._build_url('licenses')
755-
return self._iter(int(number), url, License, etag=etag,
756-
headers=License.CUSTOM_HEADERS)
761+
return self._iter(int(number), url, licenses.ShortLicense, etag=etag)
757762

758763
def login(self, username=None, password=None, token=None,
759764
two_factor_callback=None):
@@ -1205,8 +1210,7 @@ def repository(self, owner, repository):
12051210
json = None
12061211
if owner and repository:
12071212
url = self._build_url('repos', owner, repository)
1208-
json = self._json(self._get(url, headers=License.CUSTOM_HEADERS),
1209-
200)
1213+
json = self._json(self._get(url), 200)
12101214
return self._instance_or_null(repo.Repository, json)
12111215

12121216
def repository_with_id(self, number):

github3/licenses.py

+193-21
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,208 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
github3.licenses
4-
================
5-
6-
This module contains the classes relating to licenses
3+
This module contains the classes relating to licenses.
74
85
See also: https://developer.github.com/v3/licenses/
96
"""
107
from __future__ import unicode_literals
118

12-
from .models import GitHubCore
9+
import base64
10+
11+
from . import models
12+
13+
14+
class _License(models.GitHubCore):
15+
"""Base license object."""
16+
17+
class_name = '_License'
18+
19+
def _update_attributes(self, license):
20+
self._api = license['url']
21+
self.key = license['key']
22+
self.name = license['name']
23+
self.spdx_id = license['spdx_id']
24+
25+
def _repr(self):
26+
return '<{0} [{1}]>'.format(self.class_name, self.name)
27+
28+
29+
class ShortLicense(_License):
30+
"""This object represents a license returned in a collection.
31+
32+
GitHub's API returns different representations of objects in different
33+
contexts. This object reprsents a license that would be returned in a
34+
collection, e.g., retrieving all licenses from ``/licenses``.
35+
36+
This object has the following attributes:
37+
38+
.. attribute:: key
39+
40+
The short, API name, for this license.
41+
42+
.. attribute:: name
43+
44+
The long form, legal name for this license.
45+
46+
.. attribute:: spdx_id
47+
48+
The Software Package Data Exchange (a.k.a, SPDX) identifier for this
49+
license.
50+
"""
51+
52+
class_name = 'ShortLicense'
53+
54+
55+
class License(_License):
56+
"""This object represents a license as returned by the GitHub API.
57+
58+
See https://developer.github.com/v3/licenses/ for more information.
59+
60+
This object has all of the attributes of :class:`ShortLicense` as well as
61+
the following attributes:
62+
63+
.. attribute:: body
64+
65+
The full text of this license.
66+
67+
.. attribute:: conditions
68+
69+
A list of the conditions of this license.
70+
71+
.. attribute:: description
72+
73+
The short description of this license.
74+
75+
.. attribute:: featured
76+
77+
A boolean attribute describing whether this license is featured on
78+
GitHub or not.
79+
80+
.. attribute:: html_url
81+
82+
The URL to view this license on GitHub.
83+
84+
.. attribute:: implementation
85+
86+
The short description of how a user applies this license to their
87+
original work.
1388
89+
.. attribute:: limitations
1490
15-
class License(GitHubCore):
91+
A list of limitations of this license.
1692
17-
CUSTOM_HEADERS = {
18-
'Accept': 'application/vnd.github.drax-preview+json'
19-
}
93+
.. attribute:: permissions
94+
95+
A list of the permissions granted by this license.
96+
"""
2097

2198
def _update_attributes(self, license):
22-
self._api = self._get_attribute(license, 'url')
23-
self.name = self._get_attribute(license, 'name')
24-
self.permitted = self._get_attribute(license, 'permitted')
25-
self.category = self._get_attribute(license, 'category')
26-
self.forbidden = self._get_attribute(license, 'forbidden')
27-
self.featured = self._get_attribute(license, 'featured')
28-
self.html_url = self._get_attribute(license, 'html_url')
29-
self.body = self._get_attribute(license, 'body')
30-
self.key = self._get_attribute(license, 'key')
31-
self.description = self._get_attribute(license, 'description')
32-
self.implementation = self._get_attribute(license, 'implementation')
33-
self.required = self._get_attribute(license, 'required')
99+
super(License, self)._update_attributes(license)
100+
self.body = license['body']
101+
self.conditions = license['conditions']
102+
self.description = license['description']
103+
self.featured = license['featured']
104+
self.html_url = license['html_url']
105+
self.implementation = license['implementation']
106+
self.limitations = license['limitations']
107+
self.permissions = license['permissions']
34108

35109
def _repr(self):
36110
return '<License [{0}]>'.format(self.name)
111+
112+
113+
class RepositoryLicense(models.GitHubCore):
114+
"""The representation of the repository's retrieved license.
115+
116+
This object will be returned from
117+
:meth:`~github3.repos.repo.Repository.license` and behaves like
118+
:class:`~github3.repos.contents.Contents` with a few differences.
119+
This also includes a :attr:`license` attribute to access the licenses API.
120+
121+
This object has the following attributes:
122+
123+
.. attribute:: name
124+
125+
The name of the file this license is stored in on the repository.
126+
127+
.. attribute:: path
128+
129+
The path to the file of this license in the repository.
130+
131+
.. attribute:: sha
132+
133+
The current SHA of this license file in the repository.
134+
135+
.. attribute:: size
136+
137+
The size in bytes of this file.
138+
139+
.. attribute:: html_url
140+
141+
The URL used to view this file in a browser.
142+
143+
.. attribute:: git_url
144+
145+
The URL to retrieve this license file via the git protocol.
146+
147+
.. attribute:: download_url
148+
149+
The URL used to download this license file from the repository.
150+
151+
.. attribute:: type
152+
153+
Analogous to :attr:`github3.repos.contents.Contents.type`, this should
154+
indicate whether this is a file, directory, or some other type.
155+
156+
.. attribute:: content
157+
158+
The content as returned by the API. This may be base64 encoded. See
159+
:meth:`decode_content` to retrieve the content in plain-text.
160+
161+
.. attribute:: encoding
162+
163+
The encoding of the content. For example, ``base64``.
164+
165+
.. attribute:: links
166+
167+
The dictionary of URLs returned in the ``_links`` key by the API.
168+
169+
.. attribute:: license
170+
171+
A :class:`github3.licenses.ShortLicense` instance representing the
172+
metadata GitHub knows about the license.
173+
"""
174+
175+
def _update_attributes(self, license):
176+
self._api = license['url']
177+
self.name = license['name']
178+
self.path = license['path']
179+
self.sha = license['sha']
180+
self.size = license['size']
181+
self.html_url = license['html_url']
182+
self.git_url = license['git_url']
183+
self.download_url = license['download_url']
184+
self.type = license['type']
185+
self.content = license['content']
186+
self.encoding = license['encoding']
187+
self.links = license['_links']
188+
self.license = ShortLicense(license['license'], self)
189+
190+
def _repr(self):
191+
return '<RepositoryLicense [{0}]>'.format(self.name)
192+
193+
def decode_content(self):
194+
"""Decode the :attr:`content` attribute.
195+
196+
If ``content`` is base64 encoded, decode this and return it.
197+
Otherwise, return ``content``.
198+
199+
:returns:
200+
plain-text content of this license
201+
:rtype:
202+
text (unicode on Python 2, str on Python 3)
203+
"""
204+
if self.encoding == 'base64':
205+
return base64.b64decode(
206+
self.content.encode('utf-8')
207+
).decode('utf-8')
208+
return self.content

github3/repos/repo.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from ..issues.event import IssueEvent
2222
from ..issues.label import Label
2323
from ..issues.milestone import Milestone
24-
from ..licenses import License
24+
from .. import licenses
2525
from ..models import GitHubCore
2626
from .. import notifications
2727
from ..projects import Project
@@ -1356,11 +1356,11 @@ def latest_release(self):
13561356
def license(self):
13571357
"""Get the contents of a license for the repo.
13581358
1359-
:returns: :class:`License <github3.licenses.License>`
1359+
:returns: :class:`~github3.licenses.RepsoitoryLicense`
13601360
"""
13611361
url = self._build_url('license', base_url=self._api)
1362-
json = self._json(self._get(url, headers=License.CUSTOM_HEADERS), 200)
1363-
return self._instance_or_null(License, json)
1362+
json = self._json(self._get(url), 200)
1363+
return self._instance_or_null(licenses.RepositoryLicense, json)
13641364

13651365
@requires_auth
13661366
def mark_notifications(self, last_read=''):
@@ -2175,8 +2175,8 @@ class Repository(_Repository):
21752175
21762176
.. attribute:: original_license
21772177
2178-
This is the :class:`~github3.license.License` returned as part of the
2179-
repository. To retrieve the most recent license, see the
2178+
This is the :class:`~github3.license.ShortLicense` returned as part of
2179+
the repository. To retrieve the most recent license, see the
21802180
:meth:`~github3.repos.repo.Repository.license` method.
21812181
21822182
.. attribute:: mirror_url
@@ -2264,7 +2264,9 @@ def _update_attributes(self, repo):
22642264
self.language = repo['language']
22652265
self.original_license = repo['license']
22662266
if self.original_license is not None:
2267-
self.original_license = License(self.original_license, self)
2267+
self.original_license = licenses.ShortLicense(
2268+
self.original_license, self
2269+
)
22682270
self.mirror_url = repo['mirror_url']
22692271
self.network_count = repo['network_count']
22702272
self.open_issues_count = repo['open_issues_count']

tests/cassettes/GitHub_license.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": "gzip, deflate", "Accept": "application/vnd.github.drax-preview+json", "User-Agent": "github3.py/1.0.0a2", "Accept-Charset": "utf-8", "Connection": "keep-alive", "Content-Type": "application/json"}, "method": "GET", "uri": "https://api.github.com/licenses/mit"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA2VUXY+jRhD8Ky2ediXOft831sbrUTBYGN9mtZwiDGMzCWbIzHB7VpT/nurB9l4SyZKZpj+qqov5K/hDXoKn4KxcEAZ9dZY4bERBiaplbyWCo+kQa50b7NN8Xg1qdlKuHQ+zWp/n3ZRm51ODo6zcaGQTPDkzyhBV5+63zwaor1utrayudf/rMcfARtraqMEp3WNwRIM0Z2Wt+i7pWkaurRwpS7bVxlHVN+Q0gpIGrXo3I+Gok86iVA+dpEYj5+Ja1Z/oA+DpokdDtW7kdByMxhCqnDPqMPJg35NT9ejoozKm6t1lBnB15eRJG2jWj10XBuqMAWfZu+qKd2GggaSKnPzh6Kgw/sFdBlVXXXchVrihRCzidBeTNrfHmfvhHkn1noTR2pE+TigtoNZyAsugaj1cfJbvjyx+vukC8pMOPHdGuRy6CsXvF1mZbxNXTq9HYwCZOOyZvh9BhrH9lMRHegBEfrCPt1E836hT66jVXSONZVWM/HNUfu/vgerrbmzkl3ti8C0M/A6dmzJgnLM0taq6L6O32Fk36giFWELLDlD2vgkc7Xi4EsRhMOo7BPaVaHzU5qCaRsIq70Gvv3SqOqhOuQu/POiGzV2A8k+epgccHsu+7Bd3Lg/1402lTy04ZXs1HywBv7XSyMOFTmwH2YR0NFKyMHVbmZMM2YUwGjvWokAfXKV6Nl3l11b2fl3sW310cBVsAvErazXEQEMYtR7vZvLmsXAP4JfB7lpSBo9+ToOPqERzv//by7tljWQFaxY0hK14Izfzs6M7hW14uSfD8T5tyeaBrKEHG5LfCv9Lz23gLdg2pPt2EPxcTchc5rCLlR2AoYUC+Ks/b/h8EqOf/HAVynLko9Xnf3NRQHQcTY+hkIYpawjnZ/4ua3f75I+66/QHs6t13yjvoSdeHa+9OmhcG5+e7bXDjTWh4EXc7hYIcX1lW3yodJBX1TAZGiP0SQkUx4N1sAAcjBvH+JH/pTrzENYx7bJV8RrlMYkdbfPsq1jGS+wz2iFQBiG9imKd7QtCTh6lxRtlK4rSN/pFpMuQ4l+3ebzbUZaXvdhsExEjKNJFsl+K9IWeUZhmuK4FPI2+RUY889pLxChc0SbOF2u0jp5FIoq3sOxXoki56yrLKaJtlBdisU+inLb7fJvhZorSJfqmIl3lGBNv4rTArZoiRvFXHGi3jpKEZ5V9tAeBnCHSItu+5eJlXdA6S5Yxgs8xsEXPSTzNAq9FEolNSMtoE70wvpwytAE9zpsA0us65hhPjPBbFCJLmckiS4scxxBE8+Je+yp2cUhRLnasySrPNuDImqIE/dEFhWk8tWG9vUj3xSCFz3vwvqGhZRwlaIYlpRPLWzb2Gvz9D1l3uoM3BwAA", "encoding": "utf-8"}, "headers": {"vary": "Accept", "x-github-media-type": "github.drax-preview; format=json", "x-xss-protection": "1; mode=block", "x-content-type-options": "nosniff", "etag": "W/\"7752486ec4302376f72db7e4afb52535\"", "cache-control": "public, max-age=60, s-maxage=60", "status": "200 OK", "x-ratelimit-remaining": "57", "x-served-by": "bae57931a6fe678a3dffe9be8e7819c8", "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", "transfer-encoding": "chunked", "x-github-request-id": "D54E579D:7FBF:192B58FD:5645E41F", "access-control-allow-credentials": "true", "date": "Fri, 13 Nov 2015 13:22:39 GMT", "access-control-allow-origin": "*", "content-security-policy": "default-src 'none'", "content-encoding": "gzip", "strict-transport-security": "max-age=31536000; includeSubdomains; preload", "server": "GitHub.com", "x-ratelimit-limit": "60", "x-frame-options": "deny", "content-type": "application/json; charset=utf-8", "x-ratelimit-reset": "1447424220"}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/licenses/mit"}, "recorded_at": "2015-11-13T13:22:38"}], "recorded_with": "betamax/0.5.0"}
1+
{"http_interactions": [{"request": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"User-Agent": "github3.py/1.0.0a4", "Accept-Encoding": "gzip, deflate", "Accept": "application/vnd.github.drax-preview+json", "Connection": "keep-alive", "Accept-Charset": "utf-8", "Content-Type": "application/json"}, "method": "GET", "uri": "https://api.github.com/licenses/mit"}, "response": {"body": {"encoding": "utf-8", "base64_string": "H4sIAAAAAAAAA2VVXW+jSBD8Ky0/JRJrv+eN2DhGh8HCeHNRvFphGIe5xTPszLCJtbr/ftWDsbM6KVJg6I+q6urx78kPcZ48TE7STYKJKk8CL+u4oERWQlmBQ9vVH99lPZzjvTctnhvnOvswm5WdnL5J1/SHaaVPs3ZIs7OhYONO7fdbAuKrRmsrykvc/3JmaFALWxnZOakVGoVkG20claomK09dK6gT5iStlb8EXerQOyBQpVUtOc2SVu2ZjPjZSyPVG3VGWGF+lfyR9BGR3dnIt2YoOxZR2gG+nY7ka3rX5ocN6KRreZSVT8crQ2lL8ybMEECn8kwHQbW0zshD70RNvarxGWlHYYRy5IDZ+kyGqntHVvemEoBSiylYe2onhPouID43onSCSqR+ODpKEL9z5w4wWnDjUdWUxPMo3Uakzfg4dR/unqQi1wgyWjume0arz/08DtbAR/n6iOLnUQqpnPYH3HdKuejaEmBfz6I03wa1+WvVG8+Oj33R12PftoztUxC/0h0g8oO9H1vdZtDoFmJZVmEcLXSePLxOYKmTMJUs2y+9N+Mfk2CvjJKzZkg3EkMWPvhbgPTREFxMqqrta/Hl2phDWgmnDoPlmFaWB9lKd0ax99KYUuERYQdd85p82oy92qv51UZ31f0ozk0CDtlcvArfSUsNzHA40xvXFXVARyOE92PDdgoIopfqzAa3bNSDK6Vi/5Z+Wnvlp4Q6Vh8d4MEd0Ly0VkMidl2tq/7qIT87C9NgUPvJ9pKyn9z7PjWWcI/ifsrjx6s5sS+wcsVWDOAmFo5xjN69qTb4jFfJ7tkzUD7wYC9bw/+F59b1h1baJvi0JgFZPvQ3jV+rGVxiRQtgKCEB/mLLEd+we2jjfeIuQlnm897o059cJBAde6PQFNIwZQ3hfM9/RIWVvDhct61+Z3Y3uzzw6AoIUx40bpmbVYcrwsvuB3HzK10+2Qb7ybfBxW41LyOObpRAsT9YBwvA19ThchuurGEDR6pTD2EV0TZbFs9hHlG8pU2efY0X0QLzDLc42E8Ceo6LVbYrCDF5mBYvlC0pTF/orzhdBBT9vcmj7ZayfK/i9SaJIxzG6TzZLeL0iR6RmGa47mNYG3WLjLjnpVYcIXFJ6yifr1A6fIyTuHgJ9moZFylXXWY5hbQJ8yKe75Iwp80u32S4kMJ0gbppnC5ztInWUVpM0RZnFH3FC21XYZJwr70KdyCQM0SaZ5uXPH5aFbTKkkWEw8cI2MLHJBp6gdc8CeN1QItwHT4xvpwylAE9jhsA0vMq4jPuGOJvXsRZykzmWVrkeA1ANC+uuc/xNgoozOMta7LMszU4sqZIQX1UQWIaDWVYby/SdTAI4fcdeI9oaBGFCYphSOnAcozGXHG3HHG390bgV9WZXvz7H/b4WQaHBwAA", "string": ""}, "headers": {"Server": "GitHub.com", "Date": "Sun, 11 Feb 2018 12:59:42 GMT", "Content-Type": "application/json; charset=utf-8", "Transfer-Encoding": "chunked", "Status": "200 OK", "X-RateLimit-Limit": "60", "X-RateLimit-Remaining": "54", "X-RateLimit-Reset": "1518355556", "Cache-Control": "public, max-age=60, s-maxage=60", "Vary": "Accept", "ETag": "W/\"36af1ff31a1b41fb5efd9e7176e9495c\"", "X-GitHub-Media-Type": "github.v3; param=drax-preview; format=json", "Access-Control-Expose-Headers": "ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", "Access-Control-Allow-Origin": "*", "Content-Security-Policy": "default-src 'none'", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "deny", "X-XSS-Protection": "1; mode=block", "X-Runtime-rack": "0.011041", "Content-Encoding": "gzip", "X-GitHub-Request-Id": "D06A:9220:18A771A:2DC48B3:5A803E3E"}, "status": {"code": 200, "message": "OK"}, "url": "https://api.github.com/licenses/mit"}, "recorded_at": "2018-02-11T12:59:42"}], "recorded_with": "betamax/0.8.0"}

0 commit comments

Comments
 (0)