Skip to content

Commit bb382ef

Browse files
committed
lint and docstrings
1 parent 567dc68 commit bb382ef

File tree

6 files changed

+67
-29
lines changed

6 files changed

+67
-29
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include LICENSE

clickplc/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#!/usr/bin/python3
21
"""
32
A Python driver for Koyo ClickPLC ethernet units.
43

clickplc/driver.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ async def get(self, address: str = None) -> dict:
8686
'provided when driver initialized')
8787
results = {}
8888
for category, address in self.active_addresses.items():
89-
results.update(await getattr(self, '_get_' + category)(address['min'], address['max']))
89+
results.update(await getattr(self, '_get_' + category)
90+
(address['min'], address['max']))
9091
return {tag_name: results[tag_info['id'].lower()]
9192
for tag_name, tag_info in self.tags.items()}
9293

@@ -498,13 +499,15 @@ def _load_tags(self, tag_filepath: str) -> dict:
498499

499500
@staticmethod
500501
def _get_address_ranges(tags: dict) -> dict:
501-
"""Parse the loaded tags to determine the range of addresses that must be
502-
queried to return all values"""
502+
"""Determine range of addresses required.
503+
504+
Parse the loaded tags to determine the range of addresses that must be
505+
queried to return all values
506+
"""
503507
address_dict = defaultdict(lambda: {'min': 1, 'max': 1})
504508
for tag_info in tags.values():
505509
i = next(i for i, s in enumerate(tag_info['id']) if s.isdigit())
506510
category, index = tag_info['id'][:i].lower(), int(tag_info['id'][i:])
507511
address_dict[category]['min'] = min(address_dict[category]['min'], index)
508512
address_dict[category]['max'] = max(address_dict[category]['max'], index)
509513
return address_dict
510-

clickplc/mock.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
"""
2+
Python mock driver for AutomationDirect (formerly Koyo) ClickPLCs.
3+
4+
Uses local storage instead of remote communications.
5+
6+
Distributed under the GNU General Public License v2
7+
Copyright (C) 2021 NuMat Technologies
8+
"""
19
from collections import defaultdict
210
from unittest.mock import MagicMock
311

@@ -10,14 +18,16 @@
1018

1119

1220
class AsyncMock(MagicMock):
13-
"""Magic mock that works with async methods"""
21+
"""Magic mock that works with async methods."""
22+
1423
async def __call__(self, *args, **kwargs):
24+
"""Convert regular mocks into into an async coroutine."""
1525
return super(AsyncMock, self).__call__(*args, **kwargs)
1626

1727

1828
class ClickPLC(realClickPLC):
19-
"""A version of the driver with the remote communication replaced with local data storage
20-
for testing"""
29+
"""A version of the driver replacing remote communication with local storage for testing."""
30+
2131
def __init__(self, *args, **kwargs):
2232
super().__init__(*args, **kwargs)
2333
self.client = AsyncMock()

clickplc/tests/test_driver.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import asyncio
1+
"""Test the driver correctly parses a tags file and responds with correct data."""
22

33
import pytest
44

@@ -7,15 +7,19 @@
77

88
@pytest.fixture
99
def plc_driver():
10+
"""Confirm the driver correctly initializes without a tags file."""
1011
return ClickPLC('fake ip')
1112

1213

1314
@pytest.fixture
1415
def tagged_driver():
16+
"""Confirm the driver correctly initializes with a good tags file."""
1517
return ClickPLC('fake ip', 'tests/plc_tags.csv')
1618

19+
1720
@pytest.fixture
1821
def expected_tags():
22+
"""Return the tags defined in the tags file."""
1923
return {
2024
'IO2_24V_OK': {'address': {'start': 16397}, 'id': 'C13', 'type': 'bool'},
2125
'IO2_Module_OK': {'address': {'start': 16396}, 'id': 'C12', 'type': 'bool'},
@@ -38,16 +42,19 @@ def expected_tags():
3842

3943

4044
def test_get_tags(tagged_driver, expected_tags):
45+
"""Confirm that the driver returns correct values on get() calls."""
4146
assert expected_tags == tagged_driver.get_tags()
4247

4348

4449
def test_unsupported_tags():
50+
"""Confirm the driver detects an improper tags file."""
4551
with pytest.raises(TypeError, match='unsupported data type'):
4652
ClickPLC('fake ip', 'tests/bad_tags.csv')
4753

4854

4955
@pytest.mark.asyncio
5056
async def test_tagged_driver(tagged_driver, expected_tags):
57+
"""Test a roundtrip with the driver using a tags file."""
5158
await tagged_driver.set('VAH_101_OK', True)
5259
state = await tagged_driver.get()
5360
assert state.get('VAH_101_OK')
@@ -57,6 +64,7 @@ async def test_tagged_driver(tagged_driver, expected_tags):
5764
@pytest.mark.asyncio
5865
@pytest.mark.parametrize('prefix', ['x', 'y'])
5966
async def test_bool_roundtrip(plc_driver, prefix):
67+
"""Confirm x and y bools are read back correctly after being set."""
6068
await plc_driver.set(f'{prefix}2', True)
6169
await plc_driver.set(f'{prefix}3', [False, True])
6270
expected = {f'{prefix}001': False, f'{prefix}002': True, f'{prefix}003': False,
@@ -66,6 +74,7 @@ async def test_bool_roundtrip(plc_driver, prefix):
6674

6775
@pytest.mark.asyncio
6876
async def test_c_roundtrip(plc_driver):
77+
"""Confirm c bools are read back correctly after being set."""
6978
await plc_driver.set('c2', True)
7079
await plc_driver.set('c3', [False, True])
7180
expected = {'c1': False, 'c2': True, 'c3': False, 'c4': True, 'c5': False}
@@ -74,6 +83,7 @@ async def test_c_roundtrip(plc_driver):
7483

7584
@pytest.mark.asyncio
7685
async def test_df_roundtrip(plc_driver):
86+
"""Confirm df floats are read back correctly after being set."""
7787
await plc_driver.set('df2', 2.0)
7888
await plc_driver.set('df3', [3.0, 4.0])
7989
expected = {'df1': 0.0, 'df2': 2.0, 'df3': 3.0, 'df4': 4.0, 'df5': 0.0}
@@ -82,6 +92,7 @@ async def test_df_roundtrip(plc_driver):
8292

8393
@pytest.mark.asyncio
8494
async def test_ds_roundtrip(plc_driver):
95+
"""Confirm ds ints are read back correctly after being set."""
8596
await plc_driver.set('ds2', 2)
8697
await plc_driver.set('ds3', [3, 4])
8798
expected = {'ds1': 0, 'ds2': 2, 'ds3': 3, 'ds4': 4, 'ds5': 0}
@@ -90,6 +101,7 @@ async def test_ds_roundtrip(plc_driver):
90101

91102
@pytest.mark.asyncio
92103
async def test_get_error_handling(plc_driver):
104+
"""Confirm the driver gives an error on invalid get() calls."""
93105
with pytest.raises(ValueError, match='An address must be supplied'):
94106
await plc_driver.get()
95107
with pytest.raises(ValueError, match='End address must be greater than start address'):
@@ -102,68 +114,74 @@ async def test_get_error_handling(plc_driver):
102114

103115
@pytest.mark.asyncio
104116
async def test_set_error_handling(plc_driver):
117+
"""Confirm the driver gives an error on invalid set() calls."""
105118
with pytest.raises(ValueError, match='foo currently unsupported'):
106119
await plc_driver.set('foo1', 1)
107120

108121

109122
@pytest.mark.asyncio
110123
@pytest.mark.parametrize('prefix', ['x', 'y'])
111124
async def test_xy_error_handling(plc_driver, prefix):
112-
with pytest.raises(ValueError, match='address must be \*01-\*16.'):
125+
"""Ensure errors are handled for invalid requests of x and y registers."""
126+
with pytest.raises(ValueError, match=r'address must be \*01-\*16.'):
113127
await plc_driver.get(f'{prefix}17')
114-
with pytest.raises(ValueError, match='address must be in \[001, 816\].'):
128+
with pytest.raises(ValueError, match=r'address must be in \[001, 816\].'):
115129
await plc_driver.get(f'{prefix}1001')
116-
with pytest.raises(ValueError, match='address must be \*01-\*16.'):
130+
with pytest.raises(ValueError, match=r'address must be \*01-\*16.'):
117131
await plc_driver.get(f'{prefix}1-{prefix}17')
118-
with pytest.raises(ValueError, match='address must be in \[001, 816\].'):
132+
with pytest.raises(ValueError, match=r'address must be in \[001, 816\].'):
119133
await plc_driver.get(f'{prefix}1-{prefix}1001')
120-
with pytest.raises(ValueError, match='address must be \*01-\*16.'):
134+
with pytest.raises(ValueError, match=r'address must be \*01-\*16.'):
121135
await plc_driver.set(f'{prefix}17', True)
122-
with pytest.raises(ValueError, match='address must be in \[001, 816\].'):
136+
with pytest.raises(ValueError, match=r'address must be in \[001, 816\].'):
123137
await plc_driver.set(f'{prefix}1001', True)
124-
with pytest.raises(ValueError, match='Data list longer than available addresses.'):
138+
with pytest.raises(ValueError, match=r'Data list longer than available addresses.'):
125139
await plc_driver.set(f'{prefix}816', [True, True])
126140

127141

128142
@pytest.mark.asyncio
129143
async def test_c_error_handling(plc_driver):
130-
with pytest.raises(ValueError, match='C start address must be 1-2000.'):
144+
"""Ensure errors are handled for invalid requests of c registers."""
145+
with pytest.raises(ValueError, match=r'C start address must be 1-2000.'):
131146
await plc_driver.get('c2001')
132-
with pytest.raises(ValueError, match='C end address must be >start and <2000.'):
147+
with pytest.raises(ValueError, match=r'C end address must be >start and <2000.'):
133148
await plc_driver.get('c1-c2001')
134-
with pytest.raises(ValueError, match='C start address must be 1-2000.'):
149+
with pytest.raises(ValueError, match=r'C start address must be 1-2000.'):
135150
await plc_driver.set('c2001', True)
136-
with pytest.raises(ValueError, match='Data list longer than available addresses.'):
151+
with pytest.raises(ValueError, match=r'Data list longer than available addresses.'):
137152
await plc_driver.set('c2000', [True, True])
138153

139154

140155
@pytest.mark.asyncio
141156
async def test_df_error_handling(plc_driver):
142-
with pytest.raises(ValueError, match='DF must be in \[1, 500\]'):
157+
"""Ensure errors are handled for invalid requests of df registers."""
158+
with pytest.raises(ValueError, match=r'DF must be in \[1, 500\]'):
143159
await plc_driver.get('df501')
144-
with pytest.raises(ValueError, match='DF end must be in \[1, 500\]'):
160+
with pytest.raises(ValueError, match=r'DF end must be in \[1, 500\]'):
145161
await plc_driver.get('df1-df501')
146-
with pytest.raises(ValueError, match='DF must be in \[1, 500\]'):
162+
with pytest.raises(ValueError, match=r'DF must be in \[1, 500\]'):
147163
await plc_driver.set('df501', 1.0)
148-
with pytest.raises(ValueError, match='Data list longer than available addresses.'):
164+
with pytest.raises(ValueError, match=r'Data list longer than available addresses.'):
149165
await plc_driver.set('df500', [1.0, 2.0])
150166

151167

152168
@pytest.mark.asyncio
153169
async def test_ds_error_handling(plc_driver):
154-
with pytest.raises(ValueError, match='DS must be in \[1, 4500\]'):
170+
"""Ensure errors are handled for invalid requests of ds registers."""
171+
with pytest.raises(ValueError, match=r'DS must be in \[1, 4500\]'):
155172
await plc_driver.get('ds4501')
156-
with pytest.raises(ValueError, match='DS end must be in \[1, 4500\]'):
173+
with pytest.raises(ValueError, match=r'DS end must be in \[1, 4500\]'):
157174
await plc_driver.get('ds1-ds4501')
158-
with pytest.raises(ValueError, match='DS must be in \[1, 4500\]'):
175+
with pytest.raises(ValueError, match=r'DS must be in \[1, 4500\]'):
159176
await plc_driver.set('ds4501', 1)
160-
with pytest.raises(ValueError, match='Data list longer than available addresses.'):
177+
with pytest.raises(ValueError, match=r'Data list longer than available addresses.'):
161178
await plc_driver.set('ds4500', [1, 2])
162179

163180

164181
@pytest.mark.asyncio
165182
@pytest.mark.parametrize('prefix', ['x', 'y', 'c'])
166183
async def test_bool_typechecking(plc_driver, prefix):
184+
"""Ensure errors are handled for set() requests that should be bools."""
167185
with pytest.raises(ValueError, match='Expected .+ as a bool'):
168186
await plc_driver.set(f'{prefix}1', 1)
169187
with pytest.raises(ValueError, match='Expected .+ as a bool'):
@@ -172,6 +190,7 @@ async def test_bool_typechecking(plc_driver, prefix):
172190

173191
@pytest.mark.asyncio
174192
async def test_df_typechecking(plc_driver):
193+
"""Ensure errors are handled for set() requests that should be floats."""
175194
await plc_driver.set('df1', 1)
176195
with pytest.raises(ValueError, match='Expected .+ as a float'):
177196
await plc_driver.set('df1', True)
@@ -181,6 +200,7 @@ async def test_df_typechecking(plc_driver):
181200

182201
@pytest.mark.asyncio
183202
async def test_ds_typechecking(plc_driver):
203+
"""Ensure errors are handled for set() requests that should be ints."""
184204
with pytest.raises(ValueError, match='Expected .+ as a int'):
185205
await plc_driver.set('ds1', 1.0)
186206
with pytest.raises(ValueError, match='Expected .+ as a int'):

setup.cfg

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@ universal = 1
33

44
[flake8]
55
max-complexity = 15
6-
max-line-length = 89
6+
max-line-length = 99
7+
docstring-convention = pep257
8+
9+
# no docstrings in __init__.py
10+
# line breaks should be before binary operators
11+
ignore = D104,D107,W503

0 commit comments

Comments
 (0)