Skip to content

Commit e5ba343

Browse files
committed
Document pyard.* modules
1 parent 26a7dd0 commit e5ba343

File tree

4 files changed

+425
-92
lines changed

4 files changed

+425
-92
lines changed

pyard/blender.py

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,41 @@
2424

2525

2626
def blender(drb1, drb3="", drb4="", drb5=""):
27+
"""Blend DRB1 typing with DRB3/4/5 to determine expected DRBX expression
28+
29+
The DRB locus region contains multiple genes (DRB1, DRB3, DRB4, DRB5) but
30+
only certain combinations are expressed based on DRB1 allele families.
31+
This function validates that the provided DRBX typing matches the expected
32+
pattern based on DRB1 and returns the appropriate DRBX expression.
33+
34+
Args:
35+
drb1: DRB1 typing (e.g., 'DRB1*03:01+DRB1*04:01')
36+
drb3: DRB3 typing if present
37+
drb4: DRB4 typing if present
38+
drb5: DRB5 typing if present
39+
40+
Returns:
41+
Expected DRBX expression based on DRB1 families, or empty string
42+
43+
Raises:
44+
DRBXBlenderError: If provided DRBX doesn't match expected pattern
45+
"""
46+
# Parse DRB1 typing to extract allele families
2747
try:
2848
drb1_1, drb1_2 = drb1.split("+")
2949
drb1_allele_1 = drb1_1.split("*")[1]
3050
drb1_allele_2 = drb1_2.split("*")[1]
31-
drb1_fam_1 = drb1_allele_1.split(":")[0]
32-
drb1_fam_2 = drb1_allele_2.split(":")[0]
51+
drb1_fam_1 = drb1_allele_1.split(":")[0] # First field (family)
52+
drb1_fam_2 = drb1_allele_2.split(":")[0] # First field (family)
3353
except Exception:
3454
return ""
55+
# Map DRB1 families to expected DRBX genes (3, 4, 5, or 0 for none)
3556
x1 = expdrbx(drb1_fam_1)
3657
x2 = expdrbx(drb1_fam_2)
58+
# Create sorted combination code (e.g., '34', '44', '00')
3759
xx = "".join(sorted([x1, x2]))
3860

61+
# Handle case where no DRBX genes should be expressed
3962
if xx == "00":
4063
if drb3 != "":
4164
raise DRBXBlenderError("DRB3", "none")
@@ -45,17 +68,17 @@ def blender(drb1, drb3="", drb4="", drb5=""):
4568
raise DRBXBlenderError("DRB5", "none")
4669
return ""
4770

48-
# handle 03
71+
# Handle heterozygous case: one allele expresses DRB3, other doesn't
4972
if xx == "03":
5073
if drb4 != "":
5174
raise DRBXBlenderError("DRB4", "none")
5275
if drb5 != "":
5376
raise DRBXBlenderError("DRB5", "none")
5477
if drb3 != "":
55-
# if 2 copies
78+
# Check if DRB3 has two copies (homozygous DRB3-expressing alleles)
5679
drb3_g = drb3.split("+")
5780
if len(drb3_g) == 2:
58-
# homozygous, return one copy
81+
# If homozygous, return one copy
5982
if drb3_g[1] == drb3_g[0]:
6083
return drb3_g[0]
6184
else:
@@ -64,17 +87,17 @@ def blender(drb1, drb3="", drb4="", drb5=""):
6487
return drb3
6588
return ""
6689

67-
# handle 04
90+
# Handle heterozygous case: one allele expresses DRB4, other doesn't
6891
if xx == "04":
6992
if drb3 != "":
7093
raise DRBXBlenderError("DRB3", "none")
7194
if drb5 != "":
7295
raise DRBXBlenderError("DRB5", "none")
7396
if drb4 != "":
74-
# if 2 copies
97+
# Check if DRB4 has two copies (homozygous DRB4-expressing alleles)
7598
drb4_g = drb4.split("+")
7699
if len(drb4_g) == 2:
77-
# homozygous, return one copy
100+
# If homozygous, return one copy
78101
if drb4_g[1] == drb4_g[0]:
79102
return drb4_g[0]
80103
else:
@@ -83,25 +106,25 @@ def blender(drb1, drb3="", drb4="", drb5=""):
83106
return drb4
84107
return ""
85108

86-
# handle 05
109+
# Handle heterozygous case: one allele expresses DRB5, other doesn't
87110
if xx == "05":
88111
if drb3 != "":
89112
raise DRBXBlenderError("DRB3", "none")
90113
if drb4 != "":
91114
raise DRBXBlenderError("DRB4", "none")
92115
if drb5 != "":
93-
# if 2 copies
116+
# Check if DRB5 has two copies (homozygous DRB5-expressing alleles)
94117
drb5_g = drb5.split("+")
95118
if len(drb5_g) == 2:
96-
# homozygous, return one copy
119+
# If homozygous, return one copy
97120
if drb5_g[1] == drb5_g[0]:
98121
return drb5_g[0]
99122
else:
100123
raise DRBXBlenderError("DRB5 het", "hom")
101124
else:
102125
return drb5
103126
return ""
104-
# handle 33
127+
# Handle homozygous DRB3-expressing case
105128
if xx == "33":
106129
if drb4 != "":
107130
raise DRBXBlenderError("DRB4", "none")
@@ -111,7 +134,7 @@ def blender(drb1, drb3="", drb4="", drb5=""):
111134
return drb3
112135
return ""
113136

114-
# handle 44
137+
# Handle homozygous DRB4-expressing case
115138
if xx == "44":
116139
if drb3 != "":
117140
raise DRBXBlenderError("DRB3", "none")
@@ -121,7 +144,7 @@ def blender(drb1, drb3="", drb4="", drb5=""):
121144
return drb4
122145
return ""
123146

124-
# handle 55
147+
# Handle homozygous DRB5-expressing case
125148
if xx == "55":
126149
if drb3 != "":
127150
raise DRBXBlenderError("DRB3", "none")
@@ -131,28 +154,28 @@ def blender(drb1, drb3="", drb4="", drb5=""):
131154
return drb5
132155
return ""
133156

134-
# handle 34
157+
# Handle heterozygous case: one allele expresses DRB3, other expresses DRB4
135158
if xx == "34":
136159
if drb5 != "":
137160
raise DRBXBlenderError("DRB5", "none")
138161
retg = []
139162

140163
if drb3 != "":
141-
# if 2 copies
164+
# Process DRB3 typing
142165
drb3_g = drb3.split("+")
143166
if len(drb3_g) == 2:
144-
# homozygous, return one copy
167+
# If homozygous, return one copy
145168
if drb3_g[1] == drb3_g[0]:
146169
retg.append(drb3_g[0])
147170
else:
148171
raise DRBXBlenderError("DRB3 het", "hom")
149172
elif len(drb3_g) == 1:
150173
retg.append(drb3_g[0])
151174
if drb4 != "":
152-
# if 2 copies
175+
# Process DRB4 typing
153176
drb4_g = drb4.split("+")
154177
if len(drb4_g) == 2:
155-
# homozygous, return one copy
178+
# If homozygous, return one copy
156179
if drb4_g[1] == drb4_g[0]:
157180
retg.append(drb4_g[0])
158181
else:
@@ -162,28 +185,28 @@ def blender(drb1, drb3="", drb4="", drb5=""):
162185

163186
return "+".join(retg)
164187

165-
# handle 35
188+
# Handle heterozygous case: one allele expresses DRB3, other expresses DRB5
166189
if xx == "35":
167190
if drb4 != "":
168191
raise DRBXBlenderError("DRB4", "none")
169192
retg = []
170193

171194
if drb3 != "":
172-
# if 2 copies
195+
# Process DRB3 typing
173196
drb3_g = drb3.split("+")
174197
if len(drb3_g) == 2:
175-
# homozygous, return one copy
198+
# If homozygous, return one copy
176199
if drb3_g[1] == drb3_g[0]:
177200
retg.append(drb3_g[0])
178201
else:
179202
raise DRBXBlenderError("DRB3 het", "hom")
180203
elif len(drb3_g) == 1:
181204
retg.append(drb3_g[0])
182205
if drb5 != "":
183-
# if 2 copies
206+
# Process DRB5 typing
184207
drb5_g = drb5.split("+")
185208
if len(drb5_g) == 2:
186-
# homozygous, return one copy
209+
# If homozygous, return one copy
187210
if drb5_g[1] == drb5_g[0]:
188211
retg.append(drb5_g[0])
189212
else:
@@ -193,28 +216,28 @@ def blender(drb1, drb3="", drb4="", drb5=""):
193216

194217
return "+".join(retg)
195218

196-
# handle 45
219+
# Handle heterozygous case: one allele expresses DRB4, other expresses DRB5
197220
if xx == "45":
198221
if drb3 != "":
199222
raise DRBXBlenderError("DRB3", "none")
200223
retg = []
201224

202225
if drb4 != "":
203-
# if 2 copies
226+
# Process DRB4 typing
204227
drb4_g = drb4.split("+")
205228
if len(drb4_g) == 2:
206-
# homozygous, return one copy
229+
# If homozygous, return one copy
207230
if drb4_g[1] == drb4_g[0]:
208231
retg.append(drb4_g[0])
209232
else:
210233
raise DRBXBlenderError("DRB4 het", "hom")
211234
elif len(drb4_g) == 1:
212235
retg.append(drb4_g[0])
213236
if drb5 != "":
214-
# if 2 copies
237+
# Process DRB5 typing
215238
drb5_g = drb5.split("+")
216239
if len(drb5_g) == 2:
217-
# homozygous, return one copy
240+
# If homozygous, return one copy
218241
if drb5_g[1] == drb5_g[0]:
219242
retg.append(drb5_g[0])
220243
else:
@@ -229,17 +252,45 @@ def blender(drb1, drb3="", drb4="", drb5=""):
229252

230253

231254
def expdrbx(drb1_fam):
255+
"""Map DRB1 allele family to expected DRBX gene expression
256+
257+
Different DRB1 allele families are associated with expression of
258+
different DRBX genes based on linkage disequilibrium patterns.
259+
260+
Args:
261+
drb1_fam: DRB1 allele family (first field, e.g., '03', '04', '15')
262+
263+
Returns:
264+
String indicating expected DRBX gene:
265+
'3' for DRB3, '4' for DRB4, '5' for DRB5, '0' for none
266+
"""
267+
# DRB1 families associated with DRB3 expression
232268
if drb1_fam in ["03", "05", "06", "11", "12", "13", "14"]:
233269
return "3"
270+
# DRB1 families associated with DRB4 expression
234271
if drb1_fam in ["04", "07", "09"]:
235272
return "4"
273+
# DRB1 families associated with DRB5 expression
236274
if drb1_fam in ["02", "15", "16"]:
237275
return "5"
276+
# DRB1 families with no associated DRBX expression
238277
return "0"
239278

240279

241280
class DRBXBlenderError(Exception):
281+
"""Exception raised when DRBX typing doesn't match expected pattern
282+
283+
This error occurs when the provided DRB3/4/5 typing is inconsistent
284+
with what should be expressed based on the DRB1 allele families.
285+
"""
286+
242287
def __init__(self, found, expected):
288+
"""Initialize the error with found and expected values
289+
290+
Args:
291+
found: What was actually provided in the typing
292+
expected: What should have been provided based on DRB1
293+
"""
243294
self.found = found
244295
self.expected = expected
245296

0 commit comments

Comments
 (0)