Skip to content

Commit 015d56f

Browse files
committed
Move NullObject to its own module
Due to this, move tests to their own file as well.
1 parent 2080e35 commit 015d56f

7 files changed

Lines changed: 130 additions & 117 deletions

File tree

github3/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def login(username=None, password=None, token=None, two_factor_callback=None):
4646
To allow you to specify either a username and password combination or
4747
a token, none of the parameters are required. If you provide none of
4848
them, you will receive a
49-
:class:`NullObject <github3.structs.NullObject>`
49+
:class:`NullObject <github3.null.NullObject>`
5050
5151
:param str username: login name
5252
:param str password: password for the login

github3/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from github3.decorators import requires_auth
1717
from github3.exceptions import error_for
18+
from github3.null import NullObject
1819
from github3.session import GitHubSession
1920
from github3.utils import UTC
2021

@@ -139,6 +140,14 @@ def _remove_none(data):
139140
if v is None:
140141
del(data[k])
141142

143+
def _instance_or_null(self, instance_class, json):
144+
if json is None:
145+
return NullObject(str(instance_class))
146+
try:
147+
return instance_class(json, self)
148+
except TypeError: # instance_class is not a subclass of GitHubCore
149+
return instance_class(json)
150+
142151
def _json(self, response, status_code):
143152
ret = None
144153
if self._boolean(response, status_code, 404) and response.content:

github3/null.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# -*- coding: utf-8 -*-
2+
from requests.compat import is_py3
3+
4+
5+
class NullObject(object):
6+
def __init__(self, initializer=None):
7+
self.__dict__['initializer'] = initializer
8+
9+
def __int__(self):
10+
return 0
11+
12+
def __bool__(self):
13+
return False
14+
15+
__nonzero__ = __bool__
16+
17+
def __str__(self):
18+
return ''
19+
20+
def __unicode__(self):
21+
return '' if is_py3 else ''.decode()
22+
23+
def __repr__(self):
24+
return '<NullObject({0})>'.format(
25+
repr(self.__getattribute__('initializer'))
26+
)
27+
28+
def __getitem__(self, index):
29+
return self
30+
31+
def __setitem__(self, index, value):
32+
pass
33+
34+
def __getattr__(self, attr):
35+
return self
36+
37+
def __setattr__(self, attr, value):
38+
pass
39+
40+
def __call__(self, *args, **kwargs):
41+
return self
42+
43+
def __contains__(self, other):
44+
return False
45+
46+
def __iter__(self):
47+
return iter([])
48+
49+
def __next__(self):
50+
raise StopIteration
51+
52+
next = __next__
53+
54+
def is_null(self):
55+
return True

github3/structs.py

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from collections import Iterator
33
from github3.models import GitHubCore
4-
from requests.compat import is_py3, urlparse, urlencode
4+
from requests.compat import urlparse, urlencode
55

66

77
class GitHubIterator(GitHubCore, Iterator):
@@ -139,56 +139,3 @@ def _get_json(self, response):
139139
self.items = json.get('items', [])
140140
# If we return None then it will short-circuit the while loop.
141141
return json.get('items')
142-
143-
144-
class NullObject(object):
145-
def __init__(self, initializer=None):
146-
self.__dict__['initializer'] = initializer
147-
148-
def __int__(self):
149-
return 0
150-
151-
def __bool__(self):
152-
return False
153-
154-
__nonzero__ = __bool__
155-
156-
def __str__(self):
157-
return ''
158-
159-
def __unicode__(self):
160-
return '' if is_py3 else ''.decode()
161-
162-
def __repr__(self):
163-
return '<NullObject({0})>'.format(
164-
repr(self.__getattribute__('initializer'))
165-
)
166-
167-
def __getitem__(self, index):
168-
return self
169-
170-
def __setitem__(self, index, value):
171-
pass
172-
173-
def __getattr__(self, attr):
174-
return self
175-
176-
def __setattr__(self, attr, value):
177-
pass
178-
179-
def __call__(self, *args, **kwargs):
180-
return self
181-
182-
def __contains__(self, other):
183-
return False
184-
185-
def __iter__(self):
186-
return iter([])
187-
188-
def __next__(self):
189-
raise StopIteration
190-
191-
next = __next__
192-
193-
def is_null(self):
194-
return True

tests/unit/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class has a dummy ``__iter__`` implementation which we want for
155155
# Retrieve a mocked session object
156156
session = super(UnitIteratorHelper, self).create_mocked_session(*args)
157157
# Initialize a NullObject which has magical properties
158-
null = github3.structs.NullObject()
158+
null = github3.null.NullObject()
159159
# Set it as the return value for every method
160160
session.delete.return_value = null
161161
session.get.return_value = null

tests/unit/test_null.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from .helper import UnitHelper
2+
from github3.null import NullObject
3+
4+
import pytest
5+
6+
7+
class TestNullObject(UnitHelper):
8+
described_class = NullObject
9+
10+
def create_instance_of_described_class(self):
11+
return self.described_class()
12+
13+
def test_returns_empty_list(self):
14+
assert list(self.instance) == []
15+
16+
def test_contains_nothing(self):
17+
assert 'foo' not in self.instance
18+
19+
def test_returns_itself_when_called(self):
20+
assert self.instance('foo', 'bar', 'bogus') is self.instance
21+
22+
def test_returns_empty_string(self):
23+
assert str(self.instance) == ''
24+
25+
def test_allows_arbitrary_attributes(self):
26+
assert self.instance.attr is self.instance
27+
28+
def test_allows_arbitrary_attributes_to_be_set(self):
29+
self.instance.attr = 'new'
30+
assert self.instance.attr is self.instance
31+
32+
def test_provides_an_api_to_check_if_it_is_null(self):
33+
assert self.instance.is_null()
34+
35+
def test_stops_iteration(self):
36+
with pytest.raises(StopIteration):
37+
next(self.instance)
38+
39+
def test_next_raises_stops_iteration(self):
40+
with pytest.raises(StopIteration):
41+
self.instance.next()
42+
43+
def test_getitem_returns_itself(self):
44+
assert self.instance['attr'] is self.instance
45+
46+
def test_setitem_sets_nothing(self):
47+
self.instance['attr'] = 'attr'
48+
assert self.instance['attr'] is self.instance
49+
50+
def test_turns_into_unicode(self):
51+
unicode_str = b''.decode('utf-8')
52+
try:
53+
assert unicode(self.instance) == unicode_str
54+
except NameError:
55+
assert str(self.instance) == unicode_str
56+
57+
def test_instances_are_falsey(self):
58+
if self.instance:
59+
pytest.fail()
60+
61+
def test_instances_can_be_coerced_to_zero(self):
62+
assert int(self.instance) == 0

tests/unit/test_structs.py

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from .helper import UnitHelper, mock
2-
from github3.structs import GitHubIterator, NullObject
3-
4-
import pytest
2+
from github3.structs import GitHubIterator
53

64

75
class TestGitHubIterator(UnitHelper):
@@ -38,61 +36,3 @@ def test_stores_headers_properly(self):
3836
i = GitHubIterator(count, url, cls, session, headers=headers)
3937
assert i.headers != {}
4038
assert i.headers.get('Accept') == 'foo'
41-
42-
43-
class TestNullObject(UnitHelper):
44-
described_class = NullObject
45-
46-
def create_instance_of_described_class(self):
47-
return self.described_class()
48-
49-
def test_returns_empty_list(self):
50-
assert list(self.instance) == []
51-
52-
def test_contains_nothing(self):
53-
assert 'foo' not in self.instance
54-
55-
def test_returns_itself_when_called(self):
56-
assert self.instance('foo', 'bar', 'bogus') is self.instance
57-
58-
def test_returns_empty_string(self):
59-
assert str(self.instance) == ''
60-
61-
def test_allows_arbitrary_attributes(self):
62-
assert self.instance.attr is self.instance
63-
64-
def test_allows_arbitrary_attributes_to_be_set(self):
65-
self.instance.attr = 'new'
66-
assert self.instance.attr is self.instance
67-
68-
def test_provides_an_api_to_check_if_it_is_null(self):
69-
assert self.instance.is_null()
70-
71-
def test_stops_iteration(self):
72-
with pytest.raises(StopIteration):
73-
next(self.instance)
74-
75-
def test_next_raises_stops_iteration(self):
76-
with pytest.raises(StopIteration):
77-
self.instance.next()
78-
79-
def test_getitem_returns_itself(self):
80-
assert self.instance['attr'] is self.instance
81-
82-
def test_setitem_sets_nothing(self):
83-
self.instance['attr'] = 'attr'
84-
assert self.instance['attr'] is self.instance
85-
86-
def test_turns_into_unicode(self):
87-
unicode_str = b''.decode('utf-8')
88-
try:
89-
assert unicode(self.instance) == unicode_str
90-
except NameError:
91-
assert str(self.instance) == unicode_str
92-
93-
def test_instances_are_falsey(self):
94-
if self.instance:
95-
pytest.fail()
96-
97-
def test_instances_can_be_coerced_to_zero(self):
98-
assert int(self.instance) == 0

0 commit comments

Comments
 (0)