PyPaf 0.5.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.
- pypaf-0.7.0/PKG-INFO +184 -0
- pypaf-0.7.0/README.md +169 -0
- pypaf-0.7.0/src/PyPaf.egg-info/PKG-INFO +184 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/PyPaf.egg-info/SOURCES.txt +12 -2
- pypaf-0.7.0/src/paf/address.py +58 -0
- pypaf-0.7.0/src/paf/attribute.py +43 -0
- pypaf-0.7.0/src/paf/initiator.py +18 -0
- pypaf-0.7.0/src/paf/lineable.py +33 -0
- pypaf-0.7.0/src/paf/premises/attribute.py +39 -0
- pypaf-0.7.0/src/paf/premises/building_type.py +29 -0
- pypaf-0.7.0/src/paf/premises/dependent_premisable.py +34 -0
- pypaf-0.7.0/src/paf/premises/exception.py +51 -0
- pypaf-0.7.0/src/paf/premises/extender.py +24 -0
- pypaf-0.7.0/src/paf/premises/lineable.py +26 -0
- pypaf-0.7.0/src/paf/premises/premisable.py +149 -0
- pypaf-0.7.0/src/paf/premises/premises.py +97 -0
- pypaf-0.7.0/src/paf/premises/rule000.py +37 -0
- pypaf-0.7.0/src/paf/premises/rule001.py +16 -0
- pypaf-0.7.0/src/paf/premises/rule010.py +20 -0
- pypaf-0.7.0/src/paf/premises/rule011.py +16 -0
- pypaf-0.7.0/src/paf/premises/rule101.py +18 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/paf/premises/rule110.py +7 -7
- pypaf-0.7.0/src/paf/premises/rule111.py +24 -0
- pypaf-0.7.0/src/paf/premises/split.py +57 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/paf/thoroughfare_locality.py +29 -11
- pypaf-0.7.0/src/paf/version.py +3 -0
- pypaf-0.7.0/tests/test_empty.py +39 -0
- pypaf-0.7.0/tests/test_exception_i.py +51 -0
- pypaf-0.7.0/tests/test_exception_ii.py +44 -0
- pypaf-0.7.0/tests/test_exception_iii.py +45 -0
- pypaf-0.7.0/tests/test_exception_iv.py +273 -0
- pypaf-0.7.0/tests/test_mainfile.py +95 -0
- pypaf-0.7.0/tests/test_po_box.py +43 -0
- pypaf-0.7.0/tests/test_premises.py +78 -0
- pypaf-0.7.0/tests/test_rule_1.py +49 -0
- pypaf-0.7.0/tests/test_rule_2.py +45 -0
- pypaf-0.7.0/tests/test_rule_3.py +233 -0
- pypaf-0.7.0/tests/test_rule_4.py +51 -0
- pypaf-0.7.0/tests/test_rule_5.py +107 -0
- pypaf-0.7.0/tests/test_rule_6.py +166 -0
- pypaf-0.7.0/tests/test_rule_7.py +178 -0
- pypaf-0.5.0/PKG-INFO +0 -72
- pypaf-0.5.0/README.md +0 -57
- pypaf-0.5.0/src/PyPaf.egg-info/PKG-INFO +0 -72
- pypaf-0.5.0/src/paf/address.py +0 -43
- pypaf-0.5.0/src/paf/attribute.py +0 -66
- pypaf-0.5.0/src/paf/lineable.py +0 -27
- pypaf-0.5.0/src/paf/premises/common.py +0 -38
- pypaf-0.5.0/src/paf/premises/rule000.py +0 -16
- pypaf-0.5.0/src/paf/premises/rule001.py +0 -16
- pypaf-0.5.0/src/paf/premises/rule010.py +0 -55
- pypaf-0.5.0/src/paf/premises/rule011.py +0 -16
- pypaf-0.5.0/src/paf/premises/rule101.py +0 -30
- pypaf-0.5.0/src/paf/premises/rule111.py +0 -30
- pypaf-0.5.0/src/paf/premises_extender.py +0 -27
- pypaf-0.5.0/src/paf/version.py +0 -3
- pypaf-0.5.0/tests/test_exception_i.py +0 -31
- pypaf-0.5.0/tests/test_exception_ii.py +0 -29
- pypaf-0.5.0/tests/test_exception_iii.py +0 -30
- pypaf-0.5.0/tests/test_exception_iv.py +0 -100
- pypaf-0.5.0/tests/test_mainfile.py +0 -55
- pypaf-0.5.0/tests/test_po_box.py +0 -28
- pypaf-0.5.0/tests/test_rule_1.py +0 -29
- pypaf-0.5.0/tests/test_rule_2.py +0 -30
- pypaf-0.5.0/tests/test_rule_3.py +0 -109
- pypaf-0.5.0/tests/test_rule_4.py +0 -31
- pypaf-0.5.0/tests/test_rule_5.py +0 -61
- pypaf-0.5.0/tests/test_rule_6.py +0 -83
- pypaf-0.5.0/tests/test_rule_7.py +0 -91
- {pypaf-0.5.0 → pypaf-0.7.0}/LICENSE.txt +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/pyproject.toml +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/setup.cfg +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/PyPaf.egg-info/dependency_links.txt +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/PyPaf.egg-info/top_level.txt +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/paf/__init__.py +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/paf/immutable.py +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/src/paf/premises/__init__.py +0 -0
- {pypaf-0.5.0 → pypaf-0.7.0}/tests/test_immutability.py +0 -0
pypaf-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: PyPaf
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Formats the elements of a Royal Mail Postcode Address File entry
|
|
5
|
+
Author-email: John Bard <johnbard@globalnet.co.uk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/drabjay/pypaf
|
|
8
|
+
Project-URL: Repository, https://github.com/drabjay/pypaf.git
|
|
9
|
+
Project-URL: Issues, https://github.com/drabjay/paf/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
|
|
16
|
+
# PyPaf
|
|
17
|
+
|
|
18
|
+
Formats the elements of a Royal Mail Postcode Address File entry according to the rules described in the [Royal Mail Programmer's Guide Edition 7, Version 6.2](https://www.poweredbypaf.com/wp-content/uploads/2024/11/Latest-Programmers_guide_Edition-7-Version-6-2.pdf)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install it from PyPI:
|
|
23
|
+
|
|
24
|
+
pip install pypaf
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Formatting
|
|
29
|
+
|
|
30
|
+
May be used to format the PAF Address elements as a list of strings:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import paf
|
|
34
|
+
address = paf.Address({
|
|
35
|
+
'building_name': "1-2",
|
|
36
|
+
'thoroughfare_name': "NURSERY",
|
|
37
|
+
'thoroughfare_descriptor': "LANE",
|
|
38
|
+
'dependent_locality': "PENN",
|
|
39
|
+
'post_town': "HIGH WYCOMBE",
|
|
40
|
+
'postcode': "HP10 8LS"
|
|
41
|
+
})
|
|
42
|
+
address.as_list() # or list(address)
|
|
43
|
+
|
|
44
|
+
['1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS']
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or as a tuple of strings:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import paf
|
|
51
|
+
address = paf.Address({
|
|
52
|
+
'building_name': "1-2",
|
|
53
|
+
'thoroughfare_name': "NURSERY",
|
|
54
|
+
'thoroughfare_descriptor': "LANE",
|
|
55
|
+
'dependent_locality': "PENN",
|
|
56
|
+
'post_town': "HIGH WYCOMBE",
|
|
57
|
+
'postcode': "HP10 8LS"
|
|
58
|
+
})
|
|
59
|
+
address.as_tuple() # or tuple(address)
|
|
60
|
+
|
|
61
|
+
('1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or as a single string:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import paf
|
|
68
|
+
address = paf.Address({
|
|
69
|
+
'building_name': "1-2",
|
|
70
|
+
'thoroughfare_name': "NURSERY",
|
|
71
|
+
'thoroughfare_descriptor': "LANE",
|
|
72
|
+
'dependent_locality': "PENN",
|
|
73
|
+
'post_town': "HIGH WYCOMBE",
|
|
74
|
+
'postcode': "HP10 8LS"
|
|
75
|
+
})
|
|
76
|
+
address.as_str() # or str(address)
|
|
77
|
+
|
|
78
|
+
'1-2 NURSERY LANE, PENN, HIGH WYCOMBE. HP10 8LS'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or as a dictionary:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import paf
|
|
85
|
+
address = paf.Address({
|
|
86
|
+
'building_name': "1-2",
|
|
87
|
+
'thoroughfare_name': "NURSERY",
|
|
88
|
+
'thoroughfare_descriptor': "LANE",
|
|
89
|
+
'dependent_locality': "PENN",
|
|
90
|
+
'post_town': "HIGH WYCOMBE",
|
|
91
|
+
'postcode': "HP10 8LS"
|
|
92
|
+
})
|
|
93
|
+
address.as_dict()
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
'line_1': "1-2 NURSERY LANE",
|
|
97
|
+
'line_2': "PENN",
|
|
98
|
+
'post_town': "HIGH WYCOMBE",
|
|
99
|
+
'postcode': "HP10 8LS"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
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
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
After checking out the repo, run `pytest` to run the tests.
|
|
171
|
+
|
|
172
|
+
To release a new version, update the version number in `version.py`, and then run `python -m build`, which will create a distribution archive. Run `python -m twine upload dist/*`, to upload the distribution archive to [pypi.org](https://pypi.org).
|
|
173
|
+
|
|
174
|
+
## Contributing
|
|
175
|
+
|
|
176
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/drabjay/pypaf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
The package is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
181
|
+
|
|
182
|
+
## Code of Conduct
|
|
183
|
+
|
|
184
|
+
Everyone interacting in the PyPaf project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/drabjayc/pypaf/blob/master/CODE_OF_CONDUCT.md).
|
pypaf-0.7.0/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# PyPaf
|
|
2
|
+
|
|
3
|
+
Formats the elements of a Royal Mail Postcode Address File entry according to the rules described in the [Royal Mail Programmer's Guide Edition 7, Version 6.2](https://www.poweredbypaf.com/wp-content/uploads/2024/11/Latest-Programmers_guide_Edition-7-Version-6-2.pdf)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install it from PyPI:
|
|
8
|
+
|
|
9
|
+
pip install pypaf
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Formatting
|
|
14
|
+
|
|
15
|
+
May be used to format the PAF Address elements as a list of strings:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import paf
|
|
19
|
+
address = paf.Address({
|
|
20
|
+
'building_name': "1-2",
|
|
21
|
+
'thoroughfare_name': "NURSERY",
|
|
22
|
+
'thoroughfare_descriptor': "LANE",
|
|
23
|
+
'dependent_locality': "PENN",
|
|
24
|
+
'post_town': "HIGH WYCOMBE",
|
|
25
|
+
'postcode': "HP10 8LS"
|
|
26
|
+
})
|
|
27
|
+
address.as_list() # or list(address)
|
|
28
|
+
|
|
29
|
+
['1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS']
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or as a tuple of strings:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import paf
|
|
36
|
+
address = paf.Address({
|
|
37
|
+
'building_name': "1-2",
|
|
38
|
+
'thoroughfare_name': "NURSERY",
|
|
39
|
+
'thoroughfare_descriptor': "LANE",
|
|
40
|
+
'dependent_locality': "PENN",
|
|
41
|
+
'post_town': "HIGH WYCOMBE",
|
|
42
|
+
'postcode': "HP10 8LS"
|
|
43
|
+
})
|
|
44
|
+
address.as_tuple() # or tuple(address)
|
|
45
|
+
|
|
46
|
+
('1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS')
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or as a single string:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import paf
|
|
53
|
+
address = paf.Address({
|
|
54
|
+
'building_name': "1-2",
|
|
55
|
+
'thoroughfare_name': "NURSERY",
|
|
56
|
+
'thoroughfare_descriptor': "LANE",
|
|
57
|
+
'dependent_locality': "PENN",
|
|
58
|
+
'post_town': "HIGH WYCOMBE",
|
|
59
|
+
'postcode': "HP10 8LS"
|
|
60
|
+
})
|
|
61
|
+
address.as_str() # or str(address)
|
|
62
|
+
|
|
63
|
+
'1-2 NURSERY LANE, PENN, HIGH WYCOMBE. HP10 8LS'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or as a dictionary:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import paf
|
|
70
|
+
address = paf.Address({
|
|
71
|
+
'building_name': "1-2",
|
|
72
|
+
'thoroughfare_name': "NURSERY",
|
|
73
|
+
'thoroughfare_descriptor': "LANE",
|
|
74
|
+
'dependent_locality': "PENN",
|
|
75
|
+
'post_town': "HIGH WYCOMBE",
|
|
76
|
+
'postcode': "HP10 8LS"
|
|
77
|
+
})
|
|
78
|
+
address.as_dict()
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
'line_1': "1-2 NURSERY LANE",
|
|
82
|
+
'line_2': "PENN",
|
|
83
|
+
'post_town': "HIGH WYCOMBE",
|
|
84
|
+
'postcode': "HP10 8LS"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
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
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
After checking out the repo, run `pytest` to run the tests.
|
|
156
|
+
|
|
157
|
+
To release a new version, update the version number in `version.py`, and then run `python -m build`, which will create a distribution archive. Run `python -m twine upload dist/*`, to upload the distribution archive to [pypi.org](https://pypi.org).
|
|
158
|
+
|
|
159
|
+
## Contributing
|
|
160
|
+
|
|
161
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/drabjay/pypaf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
The package is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
166
|
+
|
|
167
|
+
## Code of Conduct
|
|
168
|
+
|
|
169
|
+
Everyone interacting in the PyPaf project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/drabjayc/pypaf/blob/master/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: PyPaf
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Formats the elements of a Royal Mail Postcode Address File entry
|
|
5
|
+
Author-email: John Bard <johnbard@globalnet.co.uk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/drabjay/pypaf
|
|
8
|
+
Project-URL: Repository, https://github.com/drabjay/pypaf.git
|
|
9
|
+
Project-URL: Issues, https://github.com/drabjay/paf/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
|
|
16
|
+
# PyPaf
|
|
17
|
+
|
|
18
|
+
Formats the elements of a Royal Mail Postcode Address File entry according to the rules described in the [Royal Mail Programmer's Guide Edition 7, Version 6.2](https://www.poweredbypaf.com/wp-content/uploads/2024/11/Latest-Programmers_guide_Edition-7-Version-6-2.pdf)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install it from PyPI:
|
|
23
|
+
|
|
24
|
+
pip install pypaf
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Formatting
|
|
29
|
+
|
|
30
|
+
May be used to format the PAF Address elements as a list of strings:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import paf
|
|
34
|
+
address = paf.Address({
|
|
35
|
+
'building_name': "1-2",
|
|
36
|
+
'thoroughfare_name': "NURSERY",
|
|
37
|
+
'thoroughfare_descriptor': "LANE",
|
|
38
|
+
'dependent_locality': "PENN",
|
|
39
|
+
'post_town': "HIGH WYCOMBE",
|
|
40
|
+
'postcode': "HP10 8LS"
|
|
41
|
+
})
|
|
42
|
+
address.as_list() # or list(address)
|
|
43
|
+
|
|
44
|
+
['1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS']
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or as a tuple of strings:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import paf
|
|
51
|
+
address = paf.Address({
|
|
52
|
+
'building_name': "1-2",
|
|
53
|
+
'thoroughfare_name': "NURSERY",
|
|
54
|
+
'thoroughfare_descriptor': "LANE",
|
|
55
|
+
'dependent_locality': "PENN",
|
|
56
|
+
'post_town': "HIGH WYCOMBE",
|
|
57
|
+
'postcode': "HP10 8LS"
|
|
58
|
+
})
|
|
59
|
+
address.as_tuple() # or tuple(address)
|
|
60
|
+
|
|
61
|
+
('1-2 NURSERY LANE', 'PENN', 'HIGH WYCOMBE', 'HP10 8LS')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or as a single string:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import paf
|
|
68
|
+
address = paf.Address({
|
|
69
|
+
'building_name': "1-2",
|
|
70
|
+
'thoroughfare_name': "NURSERY",
|
|
71
|
+
'thoroughfare_descriptor': "LANE",
|
|
72
|
+
'dependent_locality': "PENN",
|
|
73
|
+
'post_town': "HIGH WYCOMBE",
|
|
74
|
+
'postcode': "HP10 8LS"
|
|
75
|
+
})
|
|
76
|
+
address.as_str() # or str(address)
|
|
77
|
+
|
|
78
|
+
'1-2 NURSERY LANE, PENN, HIGH WYCOMBE. HP10 8LS'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or as a dictionary:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import paf
|
|
85
|
+
address = paf.Address({
|
|
86
|
+
'building_name': "1-2",
|
|
87
|
+
'thoroughfare_name': "NURSERY",
|
|
88
|
+
'thoroughfare_descriptor': "LANE",
|
|
89
|
+
'dependent_locality': "PENN",
|
|
90
|
+
'post_town': "HIGH WYCOMBE",
|
|
91
|
+
'postcode': "HP10 8LS"
|
|
92
|
+
})
|
|
93
|
+
address.as_dict()
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
'line_1': "1-2 NURSERY LANE",
|
|
97
|
+
'line_2': "PENN",
|
|
98
|
+
'post_town': "HIGH WYCOMBE",
|
|
99
|
+
'postcode': "HP10 8LS"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
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
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
After checking out the repo, run `pytest` to run the tests.
|
|
171
|
+
|
|
172
|
+
To release a new version, update the version number in `version.py`, and then run `python -m build`, which will create a distribution archive. Run `python -m twine upload dist/*`, to upload the distribution archive to [pypi.org](https://pypi.org).
|
|
173
|
+
|
|
174
|
+
## Contributing
|
|
175
|
+
|
|
176
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/drabjay/pypaf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
The package is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
181
|
+
|
|
182
|
+
## Code of Conduct
|
|
183
|
+
|
|
184
|
+
Everyone interacting in the PyPaf project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/drabjayc/pypaf/blob/master/CODE_OF_CONDUCT.md).
|
|
@@ -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/
|
|
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,8 @@ 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
|
|
33
|
+
tests/test_empty.py
|
|
25
34
|
tests/test_exception_i.py
|
|
26
35
|
tests/test_exception_ii.py
|
|
27
36
|
tests/test_exception_iii.py
|
|
@@ -29,6 +38,7 @@ tests/test_exception_iv.py
|
|
|
29
38
|
tests/test_immutability.py
|
|
30
39
|
tests/test_mainfile.py
|
|
31
40
|
tests/test_po_box.py
|
|
41
|
+
tests/test_premises.py
|
|
32
42
|
tests/test_rule_1.py
|
|
33
43
|
tests/test_rule_2.py
|
|
34
44
|
tests/test_rule_3.py
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""PAF Address"""
|
|
2
|
+
|
|
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
|
|
6
|
+
from .immutable import ImmutableMixin
|
|
7
|
+
from .lineable import LineableMixin
|
|
8
|
+
from .thoroughfare_locality import ThoroughfareLocalityMixin
|
|
9
|
+
from .premises.premises import Premises
|
|
10
|
+
|
|
11
|
+
class Address(ImmutableMixin, AttributeMixin, ThoroughfareLocalityMixin, LineableMixin):
|
|
12
|
+
"""Main PAF Address class"""
|
|
13
|
+
|
|
14
|
+
@attribute_init
|
|
15
|
+
def __init__(self, *args):
|
|
16
|
+
"""Initialise Address elements"""
|
|
17
|
+
object.__setattr__(self, 'premises', Premises(*args))
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
"""Return full representation of an Address"""
|
|
21
|
+
args = {k: getattr(self, k) for k in list(self.attrs) if getattr(self, k, None)}
|
|
22
|
+
return self.__class__.__name__ + '(' + str(args) + ')'
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
"""Return Address as string representation"""
|
|
26
|
+
line = ', '.join(self.lines)
|
|
27
|
+
if self.is_empty('postcode'):
|
|
28
|
+
return line
|
|
29
|
+
return '. '.join([line] + [getattr(self, 'postcode')])
|
|
30
|
+
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
"""Return Address as iterable"""
|
|
33
|
+
yield from list(self.lines)
|
|
34
|
+
if not self.is_empty('postcode'):
|
|
35
|
+
yield from list([getattr(self, 'postcode')])
|
|
36
|
+
|
|
37
|
+
def as_str(self):
|
|
38
|
+
"""Return Address as string"""
|
|
39
|
+
return str(self)
|
|
40
|
+
|
|
41
|
+
def as_list(self):
|
|
42
|
+
"""Return Address as list of strings"""
|
|
43
|
+
return list(self)
|
|
44
|
+
|
|
45
|
+
def as_tuple(self):
|
|
46
|
+
"""Return Address as tuple of strings"""
|
|
47
|
+
return tuple(self)
|
|
48
|
+
|
|
49
|
+
def as_dict(self):
|
|
50
|
+
"""Return Address as dictionary of strings"""
|
|
51
|
+
address = {}
|
|
52
|
+
for counter, line in enumerate(getattr(self, 'optional_lines'), 1):
|
|
53
|
+
address[f"line_{counter}"] = line
|
|
54
|
+
if not self.is_empty('post_town'):
|
|
55
|
+
address['post_town'] = getattr(self, 'post_town')
|
|
56
|
+
if not self.is_empty('postcode'):
|
|
57
|
+
address['postcode'] = getattr(self, 'postcode')
|
|
58
|
+
return address
|
|
@@ -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, '') == ''
|