PyPaf 0.6.0__tar.gz → 0.7.0__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.
Files changed (61) hide show
  1. {pypaf-0.6.0/src/PyPaf.egg-info → pypaf-0.7.0}/PKG-INFO +68 -1
  2. {pypaf-0.6.0 → pypaf-0.7.0}/README.md +67 -0
  3. {pypaf-0.6.0 → pypaf-0.7.0/src/PyPaf.egg-info}/PKG-INFO +68 -1
  4. {pypaf-0.6.0 → pypaf-0.7.0}/src/PyPaf.egg-info/SOURCES.txt +11 -2
  5. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/address.py +11 -11
  6. pypaf-0.7.0/src/paf/attribute.py +43 -0
  7. pypaf-0.7.0/src/paf/initiator.py +18 -0
  8. pypaf-0.7.0/src/paf/lineable.py +33 -0
  9. pypaf-0.7.0/src/paf/premises/attribute.py +39 -0
  10. pypaf-0.7.0/src/paf/premises/building_type.py +29 -0
  11. pypaf-0.7.0/src/paf/premises/dependent_premisable.py +34 -0
  12. pypaf-0.7.0/src/paf/premises/exception.py +51 -0
  13. pypaf-0.7.0/src/paf/premises/extender.py +24 -0
  14. pypaf-0.7.0/src/paf/premises/lineable.py +26 -0
  15. pypaf-0.7.0/src/paf/premises/premisable.py +149 -0
  16. pypaf-0.7.0/src/paf/premises/premises.py +97 -0
  17. pypaf-0.7.0/src/paf/premises/rule000.py +37 -0
  18. pypaf-0.7.0/src/paf/premises/rule001.py +16 -0
  19. pypaf-0.7.0/src/paf/premises/rule010.py +20 -0
  20. pypaf-0.7.0/src/paf/premises/rule011.py +16 -0
  21. pypaf-0.7.0/src/paf/premises/rule101.py +18 -0
  22. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/premises/rule110.py +7 -7
  23. pypaf-0.7.0/src/paf/premises/rule111.py +24 -0
  24. pypaf-0.7.0/src/paf/premises/split.py +57 -0
  25. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/thoroughfare_locality.py +29 -11
  26. pypaf-0.7.0/src/paf/version.py +3 -0
  27. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_empty.py +5 -0
  28. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_exception_i.py +5 -0
  29. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_exception_ii.py +5 -0
  30. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_exception_iii.py +5 -0
  31. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_exception_iv.py +83 -0
  32. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_mainfile.py +10 -0
  33. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_po_box.py +5 -0
  34. pypaf-0.7.0/tests/test_premises.py +78 -0
  35. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_1.py +5 -0
  36. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_2.py +5 -0
  37. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_3.py +30 -0
  38. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_4.py +5 -0
  39. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_5.py +12 -0
  40. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_6.py +25 -0
  41. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_rule_7.py +32 -0
  42. pypaf-0.6.0/src/paf/attribute.py +0 -66
  43. pypaf-0.6.0/src/paf/lineable.py +0 -39
  44. pypaf-0.6.0/src/paf/premises/common.py +0 -38
  45. pypaf-0.6.0/src/paf/premises/rule000.py +0 -16
  46. pypaf-0.6.0/src/paf/premises/rule001.py +0 -16
  47. pypaf-0.6.0/src/paf/premises/rule010.py +0 -55
  48. pypaf-0.6.0/src/paf/premises/rule011.py +0 -16
  49. pypaf-0.6.0/src/paf/premises/rule101.py +0 -30
  50. pypaf-0.6.0/src/paf/premises/rule111.py +0 -30
  51. pypaf-0.6.0/src/paf/premises_extender.py +0 -27
  52. pypaf-0.6.0/src/paf/version.py +0 -3
  53. {pypaf-0.6.0 → pypaf-0.7.0}/LICENSE.txt +0 -0
  54. {pypaf-0.6.0 → pypaf-0.7.0}/pyproject.toml +0 -0
  55. {pypaf-0.6.0 → pypaf-0.7.0}/setup.cfg +0 -0
  56. {pypaf-0.6.0 → pypaf-0.7.0}/src/PyPaf.egg-info/dependency_links.txt +0 -0
  57. {pypaf-0.6.0 → pypaf-0.7.0}/src/PyPaf.egg-info/top_level.txt +0 -0
  58. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/__init__.py +0 -0
  59. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/immutable.py +0 -0
  60. {pypaf-0.6.0 → pypaf-0.7.0}/src/paf/premises/__init__.py +0 -0
  61. {pypaf-0.6.0 → pypaf-0.7.0}/tests/test_immutability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyPaf
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Formats the elements of a Royal Mail Postcode Address File entry
5
5
  Author-email: John Bard <johnbard@globalnet.co.uk>
6
6
  License: MIT
@@ -25,6 +25,8 @@ Install it from PyPI:
25
25
 
26
26
  ## Usage
27
27
 
28
+ ### Formatting
29
+
28
30
  May be used to format the PAF Address elements as a list of strings:
29
31
 
30
32
  ```python
@@ -98,6 +100,71 @@ address.as_dict()
98
100
  }
99
101
  ```
100
102
 
103
+ ### Premises Attributes
104
+
105
+ The `sub_building_name`, `building_name` and `building_number` supplied in the source PAF Address elements need to be parsed according Programmer's Guide rules in order to correctly identify the premises elements of the address.
106
+
107
+ The Address class includes a parsed premises dictionary that contains key-values that may be used to identify the premises within the thoroughfare and the sub-premises within the premises.
108
+
109
+ The parsing decomposes the premises and sub-premises to its constituent parts:
110
+
111
+ | Key | Notes |
112
+ | ------------------- | ----------- |
113
+ | premises_type | If it is of a known type e.g. BLOCK, BUILDING |
114
+ | premises_number | Building number or leading digits of building name |
115
+ | premises_suffix | Non-numeric characters following leading digits of building name |
116
+ | premises_name | Building name, if it cannot be decomposed |
117
+ | sub_premises_type | If it is of a known type e.g. FLAT, UNIT |
118
+ | sub_premises_number | Leading digits of sub-building name |
119
+ | sub_premises_suffix | Non-numeric characters following leading digits of sub-building name |
120
+ | sub_premises_name | Sub-building name, if it cannot be decomposed |
121
+
122
+ ```python
123
+ import paf
124
+ self.address = paf.Address({
125
+ 'sub_building_name': "FLAT 2B",
126
+ 'building_name': "THE TOWER",
127
+ 'building_number': "27",
128
+ 'thoroughfare_name': "JOHN",
129
+ 'thoroughfare_descriptor': "STREET",
130
+ 'post_town': "WINCHESTER",
131
+ 'postcode': "SO23 9AP"
132
+ })
133
+ address.premises()
134
+
135
+ {
136
+ 'premises_number': 27,
137
+ 'premises_name': 'THE TOWER',
138
+ 'sub_premises_type': 'FLAT',
139
+ 'sub_premises_number': 2,
140
+ 'sub_premises_suffix': 'B',
141
+ }
142
+ ```
143
+
144
+ If there are no `sub_building` or `building` elements supplied the `organisation_name` or `po_box_number` elements will be used populate the premises elements, where available.
145
+
146
+ If there is no `sub_building_name` element and the `dependent_thoroughfare` elements are populated the `building` elements will be used to populate the `sub_premises` elements and the `dependent_thoroughfare` elements the `premises` elements.
147
+
148
+ ```python
149
+ import paf
150
+ self.address = paf.Address({
151
+ 'building_name': "1A",
152
+ 'dependent_thoroughfare_name': "SEASTONE",
153
+ 'dependent_thoroughfare_descriptor': "COURT",
154
+ 'thoroughfare_name': "STATION",
155
+ 'thoroughfare_descriptor': "ROAD",
156
+ 'post_town': "HOLT",
157
+ 'postcode': "NR25 7HG"
158
+ })
159
+ address.premises()
160
+
161
+ {
162
+ 'premises_name': 'SEASTONE COURT',
163
+ 'sub_premises_number': 1,
164
+ 'sub_premises_suffix': 'A'
165
+ }
166
+ ```
167
+
101
168
  ## Development
102
169
 
103
170
  After checking out the repo, run `pytest` to run the tests.
@@ -10,6 +10,8 @@ Install it from PyPI:
10
10
 
11
11
  ## Usage
12
12
 
13
+ ### Formatting
14
+
13
15
  May be used to format the PAF Address elements as a list of strings:
14
16
 
15
17
  ```python
@@ -83,6 +85,71 @@ address.as_dict()
83
85
  }
84
86
  ```
85
87
 
88
+ ### Premises Attributes
89
+
90
+ The `sub_building_name`, `building_name` and `building_number` supplied in the source PAF Address elements need to be parsed according Programmer's Guide rules in order to correctly identify the premises elements of the address.
91
+
92
+ The Address class includes a parsed premises dictionary that contains key-values that may be used to identify the premises within the thoroughfare and the sub-premises within the premises.
93
+
94
+ The parsing decomposes the premises and sub-premises to its constituent parts:
95
+
96
+ | Key | Notes |
97
+ | ------------------- | ----------- |
98
+ | premises_type | If it is of a known type e.g. BLOCK, BUILDING |
99
+ | premises_number | Building number or leading digits of building name |
100
+ | premises_suffix | Non-numeric characters following leading digits of building name |
101
+ | premises_name | Building name, if it cannot be decomposed |
102
+ | sub_premises_type | If it is of a known type e.g. FLAT, UNIT |
103
+ | sub_premises_number | Leading digits of sub-building name |
104
+ | sub_premises_suffix | Non-numeric characters following leading digits of sub-building name |
105
+ | sub_premises_name | Sub-building name, if it cannot be decomposed |
106
+
107
+ ```python
108
+ import paf
109
+ self.address = paf.Address({
110
+ 'sub_building_name': "FLAT 2B",
111
+ 'building_name': "THE TOWER",
112
+ 'building_number': "27",
113
+ 'thoroughfare_name': "JOHN",
114
+ 'thoroughfare_descriptor': "STREET",
115
+ 'post_town': "WINCHESTER",
116
+ 'postcode': "SO23 9AP"
117
+ })
118
+ address.premises()
119
+
120
+ {
121
+ 'premises_number': 27,
122
+ 'premises_name': 'THE TOWER',
123
+ 'sub_premises_type': 'FLAT',
124
+ 'sub_premises_number': 2,
125
+ 'sub_premises_suffix': 'B',
126
+ }
127
+ ```
128
+
129
+ If there are no `sub_building` or `building` elements supplied the `organisation_name` or `po_box_number` elements will be used populate the premises elements, where available.
130
+
131
+ If there is no `sub_building_name` element and the `dependent_thoroughfare` elements are populated the `building` elements will be used to populate the `sub_premises` elements and the `dependent_thoroughfare` elements the `premises` elements.
132
+
133
+ ```python
134
+ import paf
135
+ self.address = paf.Address({
136
+ 'building_name': "1A",
137
+ 'dependent_thoroughfare_name': "SEASTONE",
138
+ 'dependent_thoroughfare_descriptor': "COURT",
139
+ 'thoroughfare_name': "STATION",
140
+ 'thoroughfare_descriptor': "ROAD",
141
+ 'post_town': "HOLT",
142
+ 'postcode': "NR25 7HG"
143
+ })
144
+ address.premises()
145
+
146
+ {
147
+ 'premises_name': 'SEASTONE COURT',
148
+ 'sub_premises_number': 1,
149
+ 'sub_premises_suffix': 'A'
150
+ }
151
+ ```
152
+
86
153
  ## Development
87
154
 
88
155
  After checking out the repo, run `pytest` to run the tests.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyPaf
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Formats the elements of a Royal Mail Postcode Address File entry
5
5
  Author-email: John Bard <johnbard@globalnet.co.uk>
6
6
  License: MIT
@@ -25,6 +25,8 @@ Install it from PyPI:
25
25
 
26
26
  ## Usage
27
27
 
28
+ ### Formatting
29
+
28
30
  May be used to format the PAF Address elements as a list of strings:
29
31
 
30
32
  ```python
@@ -98,6 +100,71 @@ address.as_dict()
98
100
  }
99
101
  ```
100
102
 
103
+ ### Premises Attributes
104
+
105
+ The `sub_building_name`, `building_name` and `building_number` supplied in the source PAF Address elements need to be parsed according Programmer's Guide rules in order to correctly identify the premises elements of the address.
106
+
107
+ The Address class includes a parsed premises dictionary that contains key-values that may be used to identify the premises within the thoroughfare and the sub-premises within the premises.
108
+
109
+ The parsing decomposes the premises and sub-premises to its constituent parts:
110
+
111
+ | Key | Notes |
112
+ | ------------------- | ----------- |
113
+ | premises_type | If it is of a known type e.g. BLOCK, BUILDING |
114
+ | premises_number | Building number or leading digits of building name |
115
+ | premises_suffix | Non-numeric characters following leading digits of building name |
116
+ | premises_name | Building name, if it cannot be decomposed |
117
+ | sub_premises_type | If it is of a known type e.g. FLAT, UNIT |
118
+ | sub_premises_number | Leading digits of sub-building name |
119
+ | sub_premises_suffix | Non-numeric characters following leading digits of sub-building name |
120
+ | sub_premises_name | Sub-building name, if it cannot be decomposed |
121
+
122
+ ```python
123
+ import paf
124
+ self.address = paf.Address({
125
+ 'sub_building_name': "FLAT 2B",
126
+ 'building_name': "THE TOWER",
127
+ 'building_number': "27",
128
+ 'thoroughfare_name': "JOHN",
129
+ 'thoroughfare_descriptor': "STREET",
130
+ 'post_town': "WINCHESTER",
131
+ 'postcode': "SO23 9AP"
132
+ })
133
+ address.premises()
134
+
135
+ {
136
+ 'premises_number': 27,
137
+ 'premises_name': 'THE TOWER',
138
+ 'sub_premises_type': 'FLAT',
139
+ 'sub_premises_number': 2,
140
+ 'sub_premises_suffix': 'B',
141
+ }
142
+ ```
143
+
144
+ If there are no `sub_building` or `building` elements supplied the `organisation_name` or `po_box_number` elements will be used populate the premises elements, where available.
145
+
146
+ If there is no `sub_building_name` element and the `dependent_thoroughfare` elements are populated the `building` elements will be used to populate the `sub_premises` elements and the `dependent_thoroughfare` elements the `premises` elements.
147
+
148
+ ```python
149
+ import paf
150
+ self.address = paf.Address({
151
+ 'building_name': "1A",
152
+ 'dependent_thoroughfare_name': "SEASTONE",
153
+ 'dependent_thoroughfare_descriptor': "COURT",
154
+ 'thoroughfare_name': "STATION",
155
+ 'thoroughfare_descriptor': "ROAD",
156
+ 'post_town': "HOLT",
157
+ 'postcode': "NR25 7HG"
158
+ })
159
+ address.premises()
160
+
161
+ {
162
+ 'premises_name': 'SEASTONE COURT',
163
+ 'sub_premises_number': 1,
164
+ 'sub_premises_suffix': 'A'
165
+ }
166
+ ```
167
+
101
168
  ## Development
102
169
 
103
170
  After checking out the repo, run `pytest` to run the tests.
@@ -9,12 +9,19 @@ src/paf/__init__.py
9
9
  src/paf/address.py
10
10
  src/paf/attribute.py
11
11
  src/paf/immutable.py
12
+ src/paf/initiator.py
12
13
  src/paf/lineable.py
13
- src/paf/premises_extender.py
14
14
  src/paf/thoroughfare_locality.py
15
15
  src/paf/version.py
16
16
  src/paf/premises/__init__.py
17
- src/paf/premises/common.py
17
+ src/paf/premises/attribute.py
18
+ src/paf/premises/building_type.py
19
+ src/paf/premises/dependent_premisable.py
20
+ src/paf/premises/exception.py
21
+ src/paf/premises/extender.py
22
+ src/paf/premises/lineable.py
23
+ src/paf/premises/premisable.py
24
+ src/paf/premises/premises.py
18
25
  src/paf/premises/rule000.py
19
26
  src/paf/premises/rule001.py
20
27
  src/paf/premises/rule010.py
@@ -22,6 +29,7 @@ src/paf/premises/rule011.py
22
29
  src/paf/premises/rule101.py
23
30
  src/paf/premises/rule110.py
24
31
  src/paf/premises/rule111.py
32
+ src/paf/premises/split.py
25
33
  tests/test_empty.py
26
34
  tests/test_exception_i.py
27
35
  tests/test_exception_ii.py
@@ -30,6 +38,7 @@ tests/test_exception_iv.py
30
38
  tests/test_immutability.py
31
39
  tests/test_mainfile.py
32
40
  tests/test_po_box.py
41
+ tests/test_premises.py
33
42
  tests/test_rule_1.py
34
43
  tests/test_rule_2.py
35
44
  tests/test_rule_3.py
@@ -1,24 +1,24 @@
1
1
  """PAF Address"""
2
2
 
3
3
  # Tried using dataclasses.dataclass(frozen=True) decorator for immutablity but did not work"""
4
+ from .initiator import attribute_init
5
+ from .attribute import AttributeMixin
4
6
  from .immutable import ImmutableMixin
5
7
  from .lineable import LineableMixin
8
+ from .thoroughfare_locality import ThoroughfareLocalityMixin
9
+ from .premises.premises import Premises
6
10
 
7
- class Address(ImmutableMixin, LineableMixin):
11
+ class Address(ImmutableMixin, AttributeMixin, ThoroughfareLocalityMixin, LineableMixin):
8
12
  """Main PAF Address class"""
9
13
 
10
- def __init__(self, args):
14
+ @attribute_init
15
+ def __init__(self, *args):
11
16
  """Initialise Address elements"""
12
- for key in self.__class__.attrs: # pylint: disable=not-an-iterable
13
- object.__setattr__(self, key, '')
14
- for key, val in args.items():
15
- if hasattr(self, key):
16
- object.__setattr__(self, key, val)
17
- self.extend_premises()
17
+ object.__setattr__(self, 'premises', Premises(*args))
18
18
 
19
19
  def __repr__(self):
20
20
  """Return full representation of an Address"""
21
- args = {k: getattr(self, k) for k in self.__class__.attrs if getattr(self, k, None)} # pylint: disable=not-an-iterable
21
+ args = {k: getattr(self, k) for k in list(self.attrs) if getattr(self, k, None)}
22
22
  return self.__class__.__name__ + '(' + str(args) + ')'
23
23
 
24
24
  def __str__(self):
@@ -30,9 +30,9 @@ class Address(ImmutableMixin, LineableMixin):
30
30
 
31
31
  def __iter__(self):
32
32
  """Return Address as iterable"""
33
- yield from self.lines.__iter__()
33
+ yield from list(self.lines)
34
34
  if not self.is_empty('postcode'):
35
- yield from [getattr(self, 'postcode')].__iter__()
35
+ yield from list([getattr(self, 'postcode')])
36
36
 
37
37
  def as_str(self):
38
38
  """Return Address as string"""
@@ -0,0 +1,43 @@
1
+ """Attribute Mixin"""
2
+
3
+ class AttributeMixin():
4
+ """Address elements and derived properties"""
5
+
6
+ @classmethod
7
+ @property
8
+ def premises_attrs(cls):
9
+ """Returns Paf premises elements"""
10
+ return (
11
+ 'organisation_name', 'department_name',
12
+ 'sub_building_name', 'building_name', 'building_number',
13
+ 'po_box_number'
14
+ )
15
+
16
+ @classmethod
17
+ @property
18
+ def post_attrs(cls):
19
+ """Returns Paf post elements"""
20
+ return ('post_town', 'postcode')
21
+
22
+ @classmethod
23
+ @property
24
+ def other_attrs(cls):
25
+ """Returns Paf other elements"""
26
+ return ('concatenation_indicator', 'udprn')
27
+
28
+ @classmethod
29
+ @property
30
+ def attrs(cls):
31
+ """Returns all Paf address elements"""
32
+ return(
33
+ cls.premises_attrs
34
+ + cls.dependent_thoroughfare_attrs
35
+ + cls.thoroughfare_attrs
36
+ + cls.locality_attrs
37
+ + cls.post_attrs
38
+ + cls.other_attrs
39
+ )
40
+
41
+ def is_empty(self, attr):
42
+ """Returns if attribute value is empty"""
43
+ return getattr(self, attr, '') == ''
@@ -0,0 +1,18 @@
1
+ """Initiator Decorator"""
2
+
3
+ import functools
4
+
5
+ def attribute_init(func):
6
+ """Decorator to initiate an object based on a list of attributes"""
7
+
8
+ @functools.wraps(func)
9
+ def wrapper(self, *args, **kwargs):
10
+ """Initiate properties from specified dict"""
11
+ for key in list(getattr(self, 'attrs', [])):
12
+ object.__setattr__(self, key, '')
13
+ for key, val in args[0].items():
14
+ if hasattr(self, key):
15
+ object.__setattr__(self, key, str(val))
16
+ return func(self, *args, **kwargs)
17
+
18
+ return wrapper
@@ -0,0 +1,33 @@
1
+ """Lineable Mixin"""
2
+
3
+ from itertools import chain
4
+
5
+ class LineableMixin():
6
+ """Converts Paf address elements into list of address lines"""
7
+
8
+ @classmethod
9
+ @property
10
+ def optional_lines_attrs(cls):
11
+ """Returns optional address line attributes"""
12
+ return ('thoroughfares_and_localities',)
13
+
14
+ @classmethod
15
+ @property
16
+ def lines_attrs(cls):
17
+ """Returns optional address line attributes and post_town"""
18
+ return cls.optional_lines_attrs + ('post_town',)
19
+
20
+ @property
21
+ def optional_lines(self):
22
+ """Returns address lines, excluding post_town and postcode"""
23
+ return self.premises.lines + self._lines(self.optional_lines_attrs)
24
+
25
+ @property
26
+ def lines(self):
27
+ """Returns address lines, excluding postcode"""
28
+ return self.premises.lines + self._lines(self.lines_attrs)
29
+
30
+ def _lines(self, attrs):
31
+ """Returns list of address lines from specified attributes"""
32
+ lines = list(filter(None, [getattr(self, k, None) for k in attrs]))
33
+ return list(chain(*[line if isinstance(line, list) else [line] for line in lines]))
@@ -0,0 +1,39 @@
1
+ """Attribute Mixin"""
2
+
3
+ class AttributeMixin():
4
+ """Premises elements and derived properties"""
5
+
6
+ @classmethod
7
+ @property
8
+ def organisation_attrs(cls):
9
+ """Returns Paf organisation elements"""
10
+ return ('organisation_name', 'department_name')
11
+
12
+ @classmethod
13
+ @property
14
+ def building_attrs(cls):
15
+ """Returns Paf building elements"""
16
+ return ('sub_building_name', 'building_name', 'building_number')
17
+
18
+ @classmethod
19
+ @property
20
+ def other_attrs(cls):
21
+ """Returns Paf other elements"""
22
+ return ('po_box_number', 'concatenation_indicator')
23
+
24
+ @classmethod
25
+ @property
26
+ def attrs(cls):
27
+ """Returns all Paf premises elements"""
28
+ return(
29
+ cls.organisation_attrs
30
+ + cls.building_attrs
31
+ + cls.dependent_thoroughfare_attrs
32
+ + cls.thoroughfare_attrs
33
+ + cls.locality_attrs
34
+ + cls.other_attrs
35
+ )
36
+
37
+ def is_empty(self, attr):
38
+ """Returns if attribute value is empty"""
39
+ return getattr(self, attr, '') == ''
@@ -0,0 +1,29 @@
1
+ """Building Types"""
2
+
3
+ from .split import SplitMixin
4
+
5
+ class BuildingTypeMixin(SplitMixin):
6
+ """Determines if a string represents a known building type"""
7
+
8
+ @classmethod
9
+ @property
10
+ def known_building_types(cls):
11
+ """Returns known building types"""
12
+ return (
13
+ "BACK OF", "BLOCK", "BLOCKS", "BUILDING", "MAISONETTE", "MAISONETTES", "REAR OF",
14
+ "SHOP", "SHOPS", "STALL", "STALLS", "SUITE", "SUITES", "UNIT", "UNITS", "PO BOX"
15
+ )
16
+
17
+ @classmethod
18
+ @property
19
+ def known_sub_building_types(cls):
20
+ """Returns known sub-building types"""
21
+ return cls.known_building_types + ("FLAT", "FLATS")
22
+
23
+ def is_known_building_type(self, attr='building_name'):
24
+ """Returns if attribute starts with a known type"""
25
+ return self.but_last_word(attr) in self.known_building_types
26
+
27
+ def is_known_sub_building_type(self, attr='sub_building_name'):
28
+ """Returns if attribute starts with a known type"""
29
+ return self.but_last_word(attr) in self.known_sub_building_types
@@ -0,0 +1,34 @@
1
+ """Dependent Premisable Mixin"""
2
+
3
+ from .premisable import PremisableMixin
4
+
5
+ class DependentPremisableMixin(PremisableMixin):
6
+ """Returns the values for the premises properties"""
7
+
8
+ @property
9
+ def _premises_number(self):
10
+ """Returns premises number"""
11
+ if getattr(self, 'dependent_thoroughfare', '') != '':
12
+ return ''
13
+ return super()._premises_number
14
+
15
+ @property
16
+ def _premises_name(self):
17
+ """Returns premises name"""
18
+ if getattr(self, 'dependent_thoroughfare', '') != '':
19
+ return getattr(self, 'dependent_thoroughfare', '')
20
+ return super()._premises_name
21
+
22
+ @property
23
+ def _sub_premises_number(self):
24
+ """Returns sub-premises number"""
25
+ if getattr(self, 'dependent_thoroughfare', '') != '':
26
+ return super()._premises_number
27
+ return super()._sub_premises_number
28
+
29
+ @property
30
+ def _sub_premises_name(self):
31
+ """Returns sub-premises name"""
32
+ if getattr(self, 'dependent_thoroughfare', '') != '':
33
+ return super()._premises_name
34
+ return super()._sub_premises_name
@@ -0,0 +1,51 @@
1
+ """Exceptions"""
2
+
3
+ import re
4
+ from .building_type import BuildingTypeMixin
5
+ from .split import SplitMixin
6
+
7
+ class ExceptionMixin(BuildingTypeMixin, SplitMixin):
8
+ """Exceptions"""
9
+
10
+ @classmethod
11
+ def __is_exception_i(cls, val):
12
+ """Returns if first and last characters are numeric"""
13
+ return re.fullmatch(r'^[\d](?:.*[\d])?$', val)
14
+
15
+ @classmethod
16
+ def __is_exception_ii(cls, val):
17
+ """Returns if first and penultimate characters are numeric, and last is alphabetic"""
18
+ return re.fullmatch(r'^([\d][a-zA-Z]|[\d].*?[\d][a-zA-Z])$', val)
19
+
20
+ @classmethod
21
+ def __is_exception_iii(cls, val):
22
+ """Returns if single non-whitespace character"""
23
+ return re.fullmatch(r'^[^ \t\r\n\v\f]$', val)
24
+
25
+ @classmethod
26
+ def __is_exception(cls, val):
27
+ """Returns if value is an exception"""
28
+ return (
29
+ cls.__is_exception_i(val)
30
+ or cls.__is_exception_ii(val)
31
+ or cls.__is_exception_iii(val)
32
+ )
33
+
34
+ def __is_exception_iv(self, attr): # pylint: disable=unused-argument
35
+ """Returns if value starts with a known building type
36
+ and ends with numeric range or numeric alpha suffix"""
37
+ # Do not include suffix check as does not account for values such as BLOCK B
38
+ return self.is_known_building_type(attr)
39
+ # and re.match(r'^\d', splitstring.last_word(getattr(self, attr, None)))
40
+
41
+ def is_exception(self, attr):
42
+ """Returns if attribute is an exception"""
43
+ return self.__is_exception(getattr(self, attr, None))
44
+
45
+ def is_split_exception(self, attr):
46
+ """Returns if attribute should be split"""
47
+ return (
48
+ self.__is_exception(self.last_word(attr))
49
+ and not self.last_word(attr).isdigit()
50
+ and not self.__is_exception_iv(attr)
51
+ )
@@ -0,0 +1,24 @@
1
+ """Premises Extender Mixin"""
2
+
3
+ import sys
4
+ from .rule000 import Rule000 # pylint: disable=unused-import
5
+ from .rule001 import Rule001 # pylint: disable=unused-import
6
+ from .rule010 import Rule010 # pylint: disable=unused-import
7
+ from .rule011 import Rule011 # pylint: disable=unused-import
8
+ from .rule101 import Rule101 # pylint: disable=unused-import
9
+ from .rule110 import Rule110 # pylint: disable=unused-import
10
+ from .rule111 import Rule111 # pylint: disable=unused-import
11
+
12
+ class ExtenderMixin():
13
+ """Dynamic Premises processing"""
14
+
15
+ @property
16
+ def rule(self):
17
+ """Returns premises rule class"""
18
+ rule = ''.join(['0' if self.is_empty(k) else '1' for k in self.building_attrs])
19
+ return getattr(sys.modules[__name__], 'Rule' + rule)
20
+
21
+ def extend(self):
22
+ """Dynamically extends instance with appropriate rule"""
23
+ base_cls = self.__class__
24
+ object.__setattr__(self, '__class__', type(base_cls.__name__, (base_cls, self.rule), {}))
@@ -0,0 +1,26 @@
1
+ """Lineable Mixin"""
2
+
3
+ from itertools import chain
4
+
5
+ class LineableMixin():
6
+ """Converts Paf address premises elements into list of lines"""
7
+
8
+ @property
9
+ def lines_attrs(self):
10
+ """Returns premises line attributes"""
11
+ return self.organisation_attrs + ('po_box',) + self.rule_attrs
12
+
13
+ @property
14
+ def lines(self):
15
+ """Returns premises lines"""
16
+ return self._lines(self.lines_attrs)
17
+
18
+ @property
19
+ def po_box(self):
20
+ """Returns PO Box"""
21
+ return '' if self.is_empty('po_box_number') else f"PO BOX {getattr(self, 'po_box_number')}"
22
+
23
+ def _lines(self, attrs):
24
+ """Returns list of premises lines from specified attributes"""
25
+ lines = list(filter(None, [getattr(self, k, None) for k in attrs]))
26
+ return list(chain(*[line if isinstance(line, list) else [line] for line in lines]))