class StepwiseCommand: brief = appcli.config_attr() dirs = appcli.config_attr() usage_io = sys.stderr def __init__(self): self.quiet = False self.force_text = False
class Ivt(Main): """\ Synthesize RNA using an in vitro transcription reaction. Usage: ivt <templates>... [options] Arguments: <templates> The names to the DNA templates to transcribe. If these names can be looked up in a FreezerBox database, some parameters (e.g. template length) will automatically be determined. <%! from stepwise_mol_bio import hanging_indent %>\ Options: -p --preset <name> [default: ${app.preset}] What default reaction parameters to use. The following parameters are currently available: ${hanging_indent(app.preset_briefs, 8)} -v --volume <µL> The volume of the transcription reaction, in µL. -C --template-stock <ng/µL> The stock concentration of the template DNA (in ng/µL). By default, the concentration specified in the preset will be used. Changing this option will not change the quantity of template added to the reaction (the volume will be scaled proportionally). -t --template-mass <ng> How much template DNA to use (in ng). The template volume will be scaled in order to reach the given quantity. If the template is not concentrated enough to reach the given quantity, the reaction will just contain as much DNA as possible instead. In order to use this option, the stock concentration of the template must be in units of ng/µL. -V --template-volume <µL> The volume of DNA to add to the reaction (in µL). This will be adjusted if necessary to fit within the total volume of the reaction. -s --short Indicate that all of the templates are shorter than 300 bp. This allows the reactions to be setup with less material, so that more reactions can be performed with a single kit. -i --incubation-time <minutes> How long to incubate the transcription reaction, in minutes. -T --incubation-temp <°C> What temperature the incubate the transcription reaction at, in °C. -x --extra <percent> [default: ${app.extra_percent}] How much extra master mix to create. -R --toggle-rntp-mix Indicate whether you're using an rNTP mix, or whether you need to add each rNTP individually to the reaction. This option toggles the default value specified in the configuration file. -a --toggle-dnase-treatment Indicate whether you'd like to include the optional DNase treatment step. This option toggles the default value specified in the configuration file. Configuration: Default values for this protocol can be specified in any of the following stepwise configuration files: ${hanging_indent(app.config_paths, 8)} molbio.ivt.default_preset: The default value for the `--preset` option. molbio.ivt.rntp_mix: The default value for the `--toggle-rntp-mix` flag. molbio.ivt.dnase_treatment: The default value for the `--toggle-dnase-treament` flag. molbio.ivt.presets: Named groups of default reaction parameters. Typically each preset corresponds to a particular kit or protocol. See below for the various settings that can be specified in each preset. molbio.ivt.presets.<name>.brief: A brief description of the preset. This is displayed in the usage info for the `--preset` option. molbio.ivt.presets.<name>.inherit: Copy all settings from another preset. This can be used to make small tweaks to a protocol, e.g. "HiScribe with a non-standard additive". molbio.ivt.presets.<name>.reaction: A table detailing all the components of the transcription reaction, in the format understood by `stepwise.MasterMix.from_string()`. Optionally, this setting can be a dictionary with keys "long" and "short", each corresponding to a reaction table. This allows different reaction parameters to be used for long and short templates. The DNA template reagent must be named "template". The rNTP reagents may be marked with the "rntp" flag. The `--rntp-mix` flag will replace any reagents so marked with a single reagent named "rNTP mix". molbio.ivt.presets.<name>.instructions: A list of miscellaneous instructions pertaining to how the reaction should be set up, e.g. how to thaw the reagents, what temperature to handle the reagents at, etc. molbio.ivt.presets.<name>.extra_percent: How much extra master mix to make, as a percentage of the volume of a single reaction. molbio.ivt.presets.<name>.incubation_time_min: See `--incubation-time`. This setting can also be a dictionary with keys "long" and "short", to specify different incubation times for long and short templates. molbio.ivt.presets.<name>.incubation_temp_C: See `--incubation-temp`. molbio.ivt.presets.<name>.length_threshold The length of a template in base pairs that separates long from short templates. Template lengths will be queried from the FreezerBox database if possible. molbio.ivt.presets.<name>.dnase.default The default value for the `--dnase` flag. molbio.ivt.presets.<name>.dnase.reaction A table detailing all the components of the optional DNase reaction. One component must be named "transcription reaction". molbio.ivt.presets.<name>.dnase.incubation_time_min The incubation time (in minutes) for the optional DNase reaction. molbio.ivt.presets.<name>.dnase.incubation_temp_C The incubation temperature (in °C) for the optional DNase reaction. molbio.ivt.presets.<name>.dnase.footnotes.reaction molbio.ivt.presets.<name>.dnase.footnotes.incubation molbio.ivt.presets.<name>.dnase.footnotes.dnase Lists of footnotes for the reaction setup, incubation, and DNase treatment steps, respectively. Database: In vitro transcription reactions can appear in the "Synthesis" column of a FreezerBox database. The associated database entry will automatically be considered ssRNA, e.g. for the purpose of molecular weight calculations: ivt template=<tag> [preset=<preset>] [volume=<µL>] [time=<min>] [temp=<°C>] template=<tag> See `<templates>`. Only one template can be specified. preset=<preset> See `--preset`. volume=<µL> See `--volume`. time=<min> See `--incubation-time`. temp=<°C> See `--incubation-temp`. Template Preparation: The following information is taken directly from the HiScribe and MEGAscript manuals: Plasmid Templates: To produce RNA transcript of a defined length, plasmid DNA must be completely linearized with a restriction enzyme downstream of the insert to be transcribed. Circular plasmid templates will generate long heterogeneous RNA transcripts in higher quantities because of high processivity of T7 RNA polymerase. Be aware that there has been one report of low level transcription from the inappropriate template strand in plasmids cut with restriction enzymes leaving 3' overhanging ends [Schendorn and Mierindorf, 1985]. DNA from some miniprep procedures may be contaminated with residual RNase A. Also, restriction enzymes occasionally introduce RNase or other inhibitors of transcription. When transcription from a template is suboptimal, it is often helpful to treat the template DNA with proteinase K (100–200 μg/mL) and 0.5% SDS for 30 min at 50°C, follow this with phenol/chloroform extraction (using an equal volume) and ethanol precipitation. PCR Templates: PCR products containing T7 RNA Polymerase promoter in the correct orientation can be transcribed. Though PCR mixture can be used directly, better yields will be obtained with purified PCR products. PCR products can be purified according to the protocol for plasmid restriction digests above, or by using commercially available spin columns (we recommend Monarch PCR & DNA Cleanup Kit, NEB #T1030). PCR products should be examined on an agarose gel to estimate concentration and to confirm amplicon size prior to its use as a template. Depending on the PCR products, 0.1–0.5 μg of PCR fragments can be used in a 20 μL in vitro transcription reaction. Synthetic DNA Oligonucleotides: Synthetic DNA oligonucleotides which are either entirely double-stranded or mostly single-stranded with a double-stranded T7 promoter sequence can be used for transcription. In general, the yields are relatively low and also variable depending upon the sequence, purity, and preparation of the synthetic oligonucleotides. """ __config__ = [ DocoptConfig, MakerConfig, TemplateConfig, PresetConfig, StepwiseConfig.setup('molbio.ivt'), ] preset_briefs = appcli.config_attr() config_paths = appcli.config_attr() class Template(ShareConfigs, Argument): __config__ = [ReagentConfig] seq = appcli.param(Key(ReagentConfig, 'seq'), ) length = appcli.param( Key(ReagentConfig, 'length'), Method(lambda self: len(self.seq)), ) stock_ng_uL = appcli.param( Key(DocoptConfig, '--template-stock', cast=float), Key(ReagentConfig, 'conc_ng_uL'), default=None, ) def _calc_short(self): return all(x.length <= self.template_length_threshold for x in self.templates) def _pick_by_short(self, values): return pick_by_short(values, self.short) presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param( Key(DocoptConfig, '--preset'), Key(MakerConfig, 'preset'), Key(StepwiseConfig, 'default_preset'), ) reaction_prototype = appcli.param( Key(PresetConfig, 'reaction', cast=parse_reaction), get=_pick_by_short, ) templates = appcli.param( Key(DocoptConfig, '<templates>', cast=lambda tags: [Ivt.Template(x) for x in tags]), Key(MakerConfig, 'template', cast=lambda x: [Ivt.Template(x)]), get=bind_arguments, ) template_length_threshold = appcli.param( Key(PresetConfig, 'length_threshold'), ) template_volume_uL = appcli.param( Key(DocoptConfig, '--template-volume', cast=float), default=None, ) template_mass_ng = appcli.param( Key(DocoptConfig, '--template-mass', cast=float), default=None, ) short = appcli.param( Key(DocoptConfig, '--short'), Method(_calc_short), default=False, ) volume_uL = appcli.param( Key(DocoptConfig, '--volume-uL', cast=float), Key(MakerConfig, 'volume', cast=parse_volume_uL), Key(PresetConfig, 'volume_uL'), default=None, ) rntp_mix = appcli.toggle_param( Key(DocoptConfig, '--no-rntp-mix', toggle=True), Key(StepwiseConfig, 'rntp_mix'), default=True, ) extra_percent = appcli.param( Key(DocoptConfig, '--extra-percent'), Key(PresetConfig, 'extra_percent'), cast=float, default=10, ) instructions = appcli.param( Key(PresetConfig, 'instructions'), default_factory=list, ) incubation_times_min = appcli.param( Key(DocoptConfig, '--incubation-time'), Key(MakerConfig, 'time', cast=parse_time_m), Key(PresetConfig, 'incubation_time_min'), ) incubation_temp_C = appcli.param( Key(DocoptConfig, '--incubation-temp'), Key(MakerConfig, 'temp', cast=parse_temp_C), Key(PresetConfig, 'incubation_temp_C'), ) dnase = appcli.toggle_param( Key(DocoptConfig, '--toggle-dnase-treatment', toggle=True), Key(PresetConfig, 'dnase.treatment'), Key(StepwiseConfig, 'dnase_treatment'), default=False, ) dnase_reaction_prototype = appcli.param( Key(PresetConfig, 'dnase.reaction', cast=MasterMix), ) dnase_incubation_time_min = appcli.param( Key(PresetConfig, 'dnase.incubation_time_min'), ) dnase_incubation_temp_C = appcli.param( Key(PresetConfig, 'dnase.incubation_temp_C'), ) footnotes = appcli.param( Key(PresetConfig, 'footnotes'), default_factory=dict, ) group_by = { 'preset': group_by_identity, 'volume_uL': group_by_identity, 'incubation_times_min': group_by_identity, 'incubation_temp_C': group_by_identity, } merge_by = { 'templates': join_lists, } def __init__(self, templates): self.templates = templates def __repr__(self): return f'Ivt(templates={self.templates!r})' def get_protocol(self): p = stepwise.Protocol() ## Clean your bench p += stepwise.load('rnasezap') ## In vitro transcription rxn = self.reaction n = plural(rxn.num_reactions) f = self.footnotes.get('reaction', []) p += paragraph_list( f"Setup {n:# in vitro transcription reaction/s}{p.add_footnotes(*f)}:", rxn, unordered_list(*self.instructions), ) f = self.footnotes.get('incubation', []) if self.short and affected_by_short(self.incubation_times_min): f += [ f"Reaction time is different than usual because the template is short (<{self.template_length_threshold} bp)." ] p += f"Incubate at {self.incubation_temp_C}°C for {format_min(pick_by_short(self.incubation_times_min, self.short))}{p.add_footnotes(*f)}." ## DNase treatment if self.dnase: f = self.footnotes.get('dnase', []) p += paragraph_list( f"Setup {n:# DNase reaction/s}{p.add_footnotes(*f)}:", self.dnase_reaction, ) p += f"Incubate at {self.dnase_incubation_temp_C}°C for {format_min(self.dnase_incubation_time_min)}." return p def get_reaction(self): rxn = self.reaction_prototype.copy() rxn.num_reactions = len(self.templates) rxn.extra_percent = self.extra_percent if self.volume_uL: rxn.hold_ratios.volume = self.volume_uL, 'µL' if self.rntp_mix: rntps = [] for i, reagent in enumerate(rxn): reagent.order = i if 'rntp' in reagent.flags: rntps.append(reagent) if not rntps: err = ConfigError("cannot make rNTP mix", preset=self.preset) err.blame += "no reagents flagged as 'rntp'" err.hints += "you may need to add this information to the [molbio.ivt.{preset}] preset" raise err rxn['rNTP mix'].volume = sum(x.volume for x in rntps) rxn['rNTP mix'].stock_conc = sum(x.stock_conc for x in rntps) / len(rntps) rxn['rNTP mix'].master_mix = all(x.master_mix for x in rntps) rxn['rNTP mix'].order = rntps[0].order for rntp in rntps: del rxn[rntp.name] rxn['template'].name = ','.join(x.tag for x in self.templates) template_stocks_ng_uL = [ ng_uL for x in self.templates if (ng_uL := x.stock_ng_uL) ]
class PagePurify(Cleanup): """\ Purify nucleic acids by PAGE. Usage: page_purify <samples>... [-p <preset>] [-P <gel>] [-c <conc>] [-v <µL>] [-b <bands>] [-R | -D] [-C] Arguments: <samples> A description of each crude sample to purify. Each argument can contain several pieces of information, separated by colons as follows: name[:molecule[:conc[:volume]]] name: The name of the product. If this is corresponds to a tag in the FreezerBox database, default values for the other parameters will be read from the database. molecule: What kind of molecule the product is: either "RNA" or "DNA" (case-insensitive). Use `--rna` or `--dna` if all samples are the same molecule. conc: The concentration of the product. This may include a unit. If no unit is specified, µg/µL is assumed. Use `--conc` if all samples are the same concentration. volume: The volume of the product to load on the gel, in µL. Do not include a unit. Use `--volume` if all samples have the same volume. <%! from stepwise_mol_bio import hanging_indent %>\ Options: -p --preset <name> [default: ${app.preset}] The default parameters to use. The following presets are available: ${hanging_indent(app.preset_briefs, 8*' ')} -P --gel-preset <name> The default gel electrophoresis parameters to use. See `sw gel -h` for a list of available presets. -c --conc <float> The concentration of each sample. This may include a unit. If no unit is specified, µg/µL is assumed. This is superseded by concentrations specified in the <samples> argument, but supersedes concentrations found in the FreezerBox database. -v --volume <µL> The volume of each sample to load on the gel, in µL. Do not include a unit. This is superseded by volumes specified in the <samples> argument. -b --bands <names> A comma separated list of names identifying the bands to cut out of the gel. Typically these would be the lengths of the desired products, e.g. "400 nt" or "1.5 kb". -R --rna Assume that each sample is RNA. This is superseded by the <samples> argument, but supersedes the FreezerBox database. -D --dna Assume that each sample is DNA. This is superseded by the <samples> argument, but supersedes the FreezerBox database. -C --no-cleanup Don't include the final spin-column purification step in the protocol. Configuration: Default values for this protocol can be specified in any of the following stepwise configuration files: ${hanging_indent(app.config_paths, 8)} molbio.page_purify.default_preset: The default value for the `--preset` option. molbio.page_purify.presets: Named groups of default reaction parameters. Typically each preset corresponds to a particular kit or protocol. See below for the various settings that can be specified in each preset. molbio.page_purify.presets.<name>.conc The default value for the `--conc` option. Note that if this option is set, concentrations will never be read from the FreezerBox database. molbio.page_purify.presets.<name>.volume_uL The default value for the `--volume` option. molbio.page_purify.presets.<name>.molecule The default value for the `--rna`/`--dna` options. This should either be "RNA" or "DNA". molbio.page_purify.presets.<name>.gel_preset The default value for the `--gel-preset` option. molbio.page_purify.presets.<name>.bands The default value for the `--bands` option. molbio.page_purify.presets.<name>.cleanup_preset The default presets to use for the spin column cleanup step after recovering the DNA/RNA from the gel. See `sw spin_cleanup -h` for a list of valid presets. This option should be a dictionary with the keys 'rna' and 'dna', each specifying the preset to use with samples of the corresponding type. Alternatively, this option can be 'false' to indicate that the cleanup step should be skipped (see `--no-cleanup`). Database: PAGE purification protocols can appear in the "Cleanups" column of a FreezerBox database: page-purify [preset=<name>] [gel=<name>] [conc=<conc>] [volume=<µL>] preset=<name> See `--preset`. gel=<name> See `--gel-preset`. conc=<conc> See `--conc`. Must include a unit. volume=<µL> See `--volume`. Must include a unit. """ __config__ = [ DocoptConfig, MakerConfig, PresetConfig, StepwiseConfig.setup('molbio.page_purify'), ] Sample = Sample preset_briefs = appcli.config_attr() config_paths = appcli.config_attr() presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param( Key(DocoptConfig, '--preset'), Key(MakerConfig, 'preset'), Key(StepwiseConfig, 'default_preset'), ) samples = appcli.param( Key(DocoptConfig, '<samples>', cast=parse_samples), Method(lambda self: [Sample(name=x.tag) for x in self.products]), ) default_stock_conc = appcli.param( Key(DocoptConfig, '--conc', cast=parse_conc), Key(MakerConfig, 'conc', cast=Quantity.from_string), Key(PresetConfig, 'conc', cast=Quantity.from_string), ) default_volume_uL = appcli.param( Key(DocoptConfig, '--volume', cast=parse_volume_uL), Key(MakerConfig, 'volume', cast=parse_strict_volume_uL), Key(PresetConfig, 'volume_uL', cast=parse_volume_uL), default=5, ) default_molecule = appcli.param( Key(DocoptConfig, '--dna', cast=lambda x: 'DNA'), Key(DocoptConfig, '--rna', cast=lambda x: 'RNA'), Key(PresetConfig, 'molecule'), ) gel_preset = appcli.param( Key(DocoptConfig, '--gel-preset'), Key(MakerConfig, 'gel'), Key(PresetConfig, 'gel_preset'), ) gel_percent = appcli.param( Key(DocoptConfig, '--gel-percent'), default=None, ) gel_run_volts = appcli.param( Key(DocoptConfig, '--gel-run-volts'), default=None, ) gel_run_time_min = appcli.param( Key(DocoptConfig, '--gel-run-time-min'), default=None, ) desired_bands = appcli.param( Key(DocoptConfig, '--bands', cast=comma_list), Key(PresetConfig, 'bands'), default_factory=list, ) rna_cleanup_preset = appcli.param( Key(PresetConfig, 'cleanup_preset.rna'), ) dna_cleanup_preset = appcli.param( Key(PresetConfig, 'cleanup_preset.dna'), ) cleanup = appcli.param( Key(DocoptConfig, '--no-cleanup', cast=not_), Key(PresetConfig, 'cleanup_preset', cast=bool), ) group_by = { 'preset': group_by_identity, 'gel_preset': group_by_identity, } merge_by = { 'samples': join_lists, } def __init__(self, samples): self.samples = samples def get_protocol(self): self._bind_samples() p = stepwise.Protocol() p += self.gel_electrophoresis_steps p += self.gel_extraction_steps p += self.product_recovery_steps return p def get_gel_electrophoresis_steps(self): p = stepwise.Protocol() n = plural(self.samples) gel = Gel(self.gel_preset) mix = gel.sample_mix k = mix.volume / mix['sample'].volume for sample in self.samples: sample.stock_conc sample.sample_volume_per_lane_uL = min( sample.volume_uL, sample.volume_uL / ceil(sample.mass_ug / '20 µg'), mix['sample'].volume.value or inf, ) sample.load_volume_per_lane_uL = k * sample.sample_volume_per_lane_uL sample.num_lanes = int(ceil(sample.volume_uL / sample.sample_volume_per_lane_uL)) conc_vol_groups = group_by_identity( self.samples, key=lambda x: (x.stock_conc, x.volume_uL), ) for (conc, volume_uL), group in conc_vol_groups: prep_gel = Gel(self.gel_preset) prep_gel.num_samples = len(group) mix = prep_gel.sample_mix mix.hold_ratios.volume = k * volume_uL, 'µL' mix['sample'].name = ','.join(str(x.name) for x in group) mix['sample'].stock_conc = conc p += prep_gel.prep_step if x := self.gel_percent: gel.gel_percent = x if x := self.gel_run_volts: gel.run_volts = x
class InVitroTranslation(Main): """\ Express proteins from purified DNA templates. Usage: ivtt <templates>... [-p <name>] [-v <µL>] [-n <rxns>] [-x <percent>] [-c <nM>] [-C <nM>] [-mrIX] [-a <name;conc;vol;mm>]... [-t <time>] [-T <°C>] Arguments: <templates> The templates to express. The number of reactions will be inferred from this list. <%! from stepwise_mol_bio import hanging_indent %>\ Options: -p --preset <name> What default reaction parameters to use. The following parameters are currently available: ${hanging_indent(app.preset_briefs, 8*' ')} -v --volume <µL> The volume of the reaction in µL. By default, the volume specified by the reaction table in the chosen preset will be used. -n --num-reactions <int> The number of reactions to set up. By default, this is inferred from the number of templates. -x --extra-percent <percent> How much extra master mix to prepare, as a percentage of the minimum required master mix volume. -c --template-conc <nM> The desired final concentration of template in the reaction. -C --template-stock <nM> The stock concentration of the template DNA or mRNA, in units of nM. If not specified, a concentration will be queried from the PO₄ database. In this case, all templates must be in the database and must have identical concentrations. -m --master-mix Include the template in the master mix. -r --mrna Use mRNA as the template instead of DNA. -X --no-template Don't include the template in the reaction, e.g. as a negative control. -I --no-inhibitor Don't include RNase inhibitor in the reaction. -a --additive <name;conc;vol;mm> Add an additional reagent to the reaction. See `sw reaction -h` for a complete description of the syntax. This option can be specified multiple times. -t --incubation-time <time> [default: ${app.incubation_time}] The amount of time to incubate the reactions. No unit is assumed, so be sure to include one. If '0', the incubation step will be removed from the protocol (e.g. so it can be added back at a later point). -T --incubation-temperature <°C> [default: ${app.incubation_temp_C}] The temperature to incubate the reactions at, in °C. """ __config__ = [ DocoptConfig, PresetConfig, StepwiseConfig.setup('molbio.ivtt'), ] preset_briefs = appcli.config_attr() preset_brief_template = '{kit}' class Template(ShareConfigs, Argument): __config__ = [ ReagentConfig.setup( db_getter=lambda self: self.db, ), ] stock_nM = appcli.param( Key(DocoptConfig, '--template-stock', cast=float), Key(ReagentConfig, 'conc_nM'), Key(PresetConfig, 'template_stock_nM'), ) is_mrna = appcli.param( Key(DocoptConfig, '--mrna'), Key(ReagentConfig, 'molecule', cast=lambda x: x == 'RNA'), ) presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param( Key(DocoptConfig, '--preset'), Key(StepwiseConfig, 'default_preset'), ) base_reaction = appcli.param( Key(PresetConfig, 'reaction'), cast=stepwise.MasterMix.from_text, ) title = appcli.param( Key(PresetConfig, 'title'), Key(PresetConfig, 'kit'), ) templates = appcli.param( Key(DocoptConfig, parse_templates_from_docopt), get=bind_arguments, ) volume_uL = appcli.param( Key(DocoptConfig, '--volume', cast=eval), default=None, ) default_volume_uL = appcli.param( # The difference between `default_volume_uL` and `volume_uL` is # that the default additives are applied to the reaction after the # default volume is set, but before the non-default volume is set. # This allows the volume of the additive to be scaled # proportionally to the volume of the reaction that the additive # was specified for. Key(PresetConfig, 'volume_uL'), Key(StepwiseConfig, 'default_volume_uL'), default=None, ) num_reactions = appcli.param( Key(DocoptConfig, '--num-reactions', cast=eval), default=None, get=lambda self, x: x or len(self.templates), ) extra_percent = appcli.param( Key(DocoptConfig, '--extra-percent', cast=float), default=10, ) template_conc_nM = appcli.param( Key(DocoptConfig, '--template-conc', cast=float), Key(PresetConfig, 'template_conc_nM'), default=None, ) master_mix = appcli.param( Key(DocoptConfig, '--master-mix'), default=False, ) use_template = appcli.param( Key(DocoptConfig, '--no-template', cast=not_), default=True, ) use_rnase_inhibitor = appcli.param( Key(DocoptConfig, '--no-inhibitor', cast=not_), default=True, ) additives = appcli.param( Key(DocoptConfig, '--additive'), default_factory=list, ) default_additives = appcli.param( Key(PresetConfig, 'additives'), default_factory=list, ) setup_instructions = appcli.param( Key(PresetConfig, 'setup_instructions'), default_factory=list, ) setup_footnote = appcli.param( Key(PresetConfig, 'setup_footnote'), default=None, ) incubation_time = appcli.param( Key(DocoptConfig, '--incubation-time'), Key(PresetConfig, 'incubation_time'), ) incubation_temp_C = appcli.param( Key(DocoptConfig, '--incubation-temp'), Key(PresetConfig, 'incubation_temp_C'), cast=float, ) incubation_footnote = appcli.param( Key(PresetConfig, 'incubation_footnote'), default=None, ) def get_protocol(self): p = stepwise.Protocol() rxn = self.reaction p += pl( f"Setup {plural(self.num_reactions):# {self.title} reaction/s}{p.add_footnotes(self.setup_footnote)}:", rxn, ul(*self.setup_instructions), ) if self.incubation_time != '0': p += f"Incubate at {self.incubation_temp_C:g}°C for {self.incubation_time}{p.add_footnotes(self.incubation_footnote)}." return p def get_reaction(self): def add_reagents(additives): nonlocal i # It would be better if there was a utility in stepwise for parsing # `sw reaction`-style strings. Maybe `Reagent.from_text()`. for i, additive in enumerate(additives, i): reagent, stock_conc, volume, master_mix = additive.split(';') rxn[reagent].stock_conc = stock_conc rxn[reagent].volume = volume rxn[reagent].master_mix = {'+': True, '-': False, '': False}[master_mix.strip()] rxn[reagent].order = i rxn = deepcopy(self.base_reaction) rxn.num_reactions = self.num_reactions for i, reagent in enumerate(rxn): reagent.order = i if self.default_volume_uL: rxn.hold_ratios.volume = self.default_volume_uL, 'µL' add_reagents(self.default_additives) if self.volume_uL: rxn.hold_ratios.volume = self.volume_uL, 'µL' add_reagents(self.additives) if self.use_mrna: template = 'mRNA' require_reagent(rxn, 'mRNA') del_reagent_if_present(rxn, 'DNA') del_reagents_by_flag(rxn, 'dna') else: template = 'DNA' require_reagent(rxn, 'DNA') del_reagent_if_present(rxn, 'mRNA') del_reagents_by_flag(rxn, 'mrna') rxn[template].name = f"{','.join(self.templates)}" rxn[template].master_mix = self.master_mix rxn[template].hold_conc.stock_conc = self.template_stock_nM, 'nM' if self.template_conc_nM: rxn[template].hold_stock_conc.conc = self.template_conc_nM, 'nM' elif self.use_template: warn("Template concentrations must be empirically optimized.\nThe default value is just a plausible starting point.") if not self.use_template: del rxn[template] if not self.use_rnase_inhibitor: del_reagents_by_flag(rxn, 'rnase') # Make sure the template is added last. rxn[template].order = i+1 if self.use_template: rxn.fix_volumes(template) rxn.extra_percent = self.extra_percent return rxn def get_template_stock_nM(self): return min(x.stock_nM for x in self.templates) @property def use_mrna(self): return unanimous(x.is_mrna for x in self.templates)
class LaserScanner(Main): """\ Image a gel using a laser scanner. Usage: laser_scanner <optics>... <%! from stepwise_mol_bio import hanging_indent %>\ Arguments: <optics> A laser/filter combination to use. The most convenient way to specify such a combination is to give the name of a preset. The following presets are currently available: ${hanging_indent(app.preset_briefs, 8*' ')} You can define new presets by adding blocks like the following to your stepwise configuration file: [molbio.laser.presets.name] laser = <int> or <list of ints> filter = <str> or <list of strs> You can also explicitly provide laser and filter parameters using the following syntax: <laser>/<filter> """ __config__ = [ DocoptConfig, PresetConfig, StepwiseConfig.setup('molbio.laser'), ] presets = appcli.param( appcli.Key(StepwiseConfig, 'presets'), pick=list, ) preset_briefs = appcli.config_attr() preset_brief_template = '{laser} nm' # This attribute is a list of: # - name of preset (str) # - laser/filter (str) # - {laser: filter} (dict) optics = appcli.param( appcli.Key(DocoptConfig, '<optics>'), default_factory=list, ) def __init__(self, *optics): self.optics = list(optics) @classmethod def from_laser_filter_pair(cls, laser, filter): self = cls() self.optics.append({ 'laser': laser, 'filter': filter, }) return self def get_protocol(self): optics = [self.parse_optics(x) for x in self.optics] lasers = [f"{plural(optics):laser/s}:" ] + [f"{x['laser']} nm" for x in optics] filters = [f"{plural(optics):filter/s}:" ] + [x['filter'] for x in optics] p = stepwise.Protocol() p += pl( "Image with a laser scanner:", table([lasers, filters], align='<>>'), ) return p def parse_optics(self, optics): if isinstance(optics, dict): return optics try: # Might be good to make it so that the `presets` attribute it a # `Presets` instance rather than a list. This would affect a bunch # of protocol, though. return Presets(self.presets)[optics] except KeyError: try: laser, filter = optics.split('/') return {'laser': laser, 'filter': filter} except ValueError as err: raise UsageError( f"expected a preset or '<laser>/<filter>', got {optics!r}" ) from None
class SpinCleanup(Cleanup): """\ Purify a PCR reaction using a silica spin column. Usage: spin_cleanup [<preset>] [-s <µL>] [-d <buffer>] [-v <µL>] <%! from stepwise_mol_bio import hanging_indent %>\ Arguments: <preset> [default: ${app.preset}] The default parameters to use. Typically these correspond to commercial kits: ${hanging_indent(app.preset_briefs, 8*' ')} Options: -s --sample-volume <µL> The volume of the sample, in µL. -d --elute-buffer <name> The buffer to elute in. -v --elute-volume <µL> The volume of purified DNA/RNA to elute, in µL. The default value depends on the preset, but can usually be lowered to get more concentrated product. A warning will be displayed if the requested volume is lower than the minimum recommended by the kit manufacturer. Configuration: Default values for this protocol can be specified in any of the following stepwise configuration files: ${hanging_indent(app.config_paths, 8)} molbio.spin_cleanup.default_preset: The default value for the `--preset` option. molbio.spin_cleanup.presets: Named groups of default reaction parameters. Typically each preset corresponds to a particular kit or protocol. See below for the various settings that can be specified in each preset. molbio.spin_cleanup.presets.<name>.protocol_name How to refer to the whole protocol. Commonly this is the name of the spin column kit. molbio.spin_cleanup.presets.<name>.protocol_link A link (typically minified) to the complete protocol, e.g. as published by the manufacturer of the columns. This is not required, but if specified, will be included in the protocol as a footnote. molbio.spin_cleanup.presets.<name>.column_name How to refer to the specific spin column used in the protocol. molbio.spin_cleanup.presets.<name>.spin_speed_g How fast to spin the column in each centrifugation step, in units of g-force. molbio.spin_cleanup.presets.<name>.column_capacity_ug The maximum binding capacity of the column, in µg. This information is added to the protocol as a footnote. molbio.spin_cleanup.presets.<name>.sample_type How to generically refer to the sample in the protocol, e.g. "DNA". molbio.spin_cleanup.presets.<name>.sample_volume_uL The volume of sample to load on the column, in µL. Alternatively, this can be a dictionary with keys 'min' and/or 'max' specifying the minimum and maximum allowed sample volumes, respectively. molbio.spin_cleanup.presets.<name>.bind_buffer The name(s) of the buffer(s) to use to bind the sample to column. This can be either a string or a list of strings. Use a list to specify that multiple buffers (e.g. binding buffer and ethanol) should be mixed with the sample before it is loaded on the column. If this option is a list, the `bind_volume_uL` and `bind_volume_x` options must also be lists of the same length (or left unspecified). molbio.spin_cleanup.presets.<name>.bind_volume_uL How much `bind_buffer` to use, in µL. This can be either a number or a list of numbers; see `bind_buffer` for more details. This takes precedence over the `bind_volume_x` setting. molbio.spin_cleanup.presets.<name>.bind_volume_x How much `bind_buffer` to use, as a multiple of the sample volume. This can be a number or a list of numbers; see `bind_buffer` for more details. This is superseded by the `bind_volume_uL` setting. molbio.spin_cleanup.presets.<name>.bind_spin_sec How long to centrifuge the column during the bind step. molbio.spin_cleanup.presets.<name>.bind_vacuum Whether or not to use a vacuum manifold for the bind step. The default is False. If True, the `bind_spin_sec` option is ignored. molbio.spin_cleanup.presets.<name>.pH_buffer The name of the buffer to use when adjusting the pH of the sample. molbio.spin_cleanup.presets.<name>.pH_volume_uL How much `pH_buffer` to use, in µL. This takes precedence over the `pH_volume_x` setting. molbio.spin_cleanup.presets.<name>.pH_volume_x How much `pH_buffer` to use, as a multiple of the sample volume. This is superseded by the `pH_volume_uL` setting. molbio.spin_cleanup.presets.<name>.pH_color The color the sample/binding buffer should be after reaching the correct pH. molbio.spin_cleanup.presets.<name>.wash_buffer The name of the buffer to use when washing the column. This can either be a string or a list of strings. Use a list to specify that there should be multiple wash steps. If this option is a list, the `wash_volume_uL`, `wash_spin_sec`, and `wash_vacuum` options must also be lists of the same length (or left unspecified). molbio.spin_cleanup.presets.<name>.wash_volume_uL The volume of `wash_buffer` to use, in µL. This can either be a number or a list of numbers; see `wash_buffer` for more details. molbio.spin_cleanup.presets.<name>.wash_spin_sec How long to centrifuge the column during the wash step. This can either be a number or a list of numbers; see `wash_buffer` for more details. molbio.spin_cleanup.presets.<name>.wash_vacuum Whether or not to use a vacuum manifold for the wash step. This can either be a boolean or a list of booleans; see `wash_buffer` for more details. The default is False. If True, the `wash_spin_sec` option is ignored. molbio.spin_cleanup.presets.<name>.dry_spin_sec How long to centrifuge the column after the wash step(s), e.g. to remove any residual ethanol. If left unspecified, this step will not be included in the protocol. molbio.spin_cleanup.presets.<name>.elute_buffer The default value for the `--elute-buffer` flag. molbio.spin_cleanup.presets.<name>.elute_volume_uL The default value for the `--elute-volume` flag. molbio.spin_cleanup.presets.<name>.elute_min_volume_uL The minimum recommended volume to elute in. Smaller volumes can still be specified, but will be accompanied by a warning. molbio.spin_cleanup.presets.<name>.elute_wait_sec How long to incubate the column with elution buffer before eluting, in seconds. molbio.spin_cleanup.presets.<name>.elute_spin_sec How long to centrifuge the column when eluting. Database: Spin-column cleanup protocols can appear in the "Cleanups" column of a FreezerBox database: spin-cleanup [<preset>] [volume=<µL>] [buffer=<name>] <preset> See `<preset>`. volume=<µL> See `--elute-volume`. Must specify a unit. buffer=<µL> See `--elute-buffer`. """ __config__ = [ DocoptConfig, MakerConfig, PresetConfig, StepwiseConfig.setup('molbio.spin_cleanup'), ] preset_briefs = appcli.config_attr() config_paths = appcli.config_attr() preset_brief_template = '{protocol_name}' presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param( Key(DocoptConfig, '<preset>'), Key(MakerConfig, 1), Key(StepwiseConfig, 'default_preset'), ) protocol_name = appcli.param( Key(PresetConfig, 'protocol_name'), ) protocol_link = appcli.param( Key(PresetConfig, 'protocol_link'), default=None, ) column_name = appcli.param( Key(PresetConfig, 'column_name'), default='silica spin column', ) spin_speed_g = appcli.param( Key(PresetConfig, 'spin_speed_g'), default=None, ) column_capacity_ug = appcli.param( Key(PresetConfig, 'column_capacity_ug'), default=None, ) sample_type = appcli.param( Key(PresetConfig, 'sample_type'), default='DNA', ) sample_volume_uL = appcli.param( Key(DocoptConfig, '--sample-volume', cast=float), default=None, ) target_sample_volume_uL = appcli.param( Key(PresetConfig, 'sample_volume_uL'), default=None, ) bind_buffer = appcli.param( Key(PresetConfig, 'bind_buffer'), ) bind_volume_uL = appcli.param( Key(PresetConfig, 'bind_volume_uL'), default=None ) bind_volume_x = appcli.param( Key(PresetConfig, 'bind_volume_x'), default=None ) bind_spin_sec = appcli.param( Key(PresetConfig, 'bind_spin_sec'), default=None ) bind_vacuum = appcli.param( Key(PresetConfig, 'bind_vacuum'), default=False, ) ph_buffer = appcli.param( Key(PresetConfig, 'pH_buffer'), default=None, ) ph_volume_uL = appcli.param( Key(PresetConfig, 'pH_volume_uL'), default=None ) ph_volume_x = appcli.param( Key(PresetConfig, 'pH_volume_x'), default=None ) ph_color = appcli.param( Key(PresetConfig, 'pH_color'), ) wash_buffer = appcli.param( Key(PresetConfig, 'wash_buffer'), ) wash_volume_uL = appcli.param( Key(PresetConfig, 'wash_volume_uL'), ) wash_spin_sec = appcli.param( Key(PresetConfig, 'wash_spin_sec'), default=None, ) wash_vacuum = appcli.param( Key(PresetConfig, 'wash_vacuum'), default=False, ) dry_spin_sec = appcli.param( Key(PresetConfig, 'dry_spin_sec'), default=None, ) elute_buffer = appcli.param( Key(DocoptConfig, '--elute-buffer'), Key(MakerConfig, 'buffer'), Key(PresetConfig, 'elute_buffer'), ) elute_volume_uL = appcli.param( Key(DocoptConfig, '--elute-volume', cast=float), Key(MakerConfig, 'volume', cast=parse_volume_uL), Key(PresetConfig, 'elute_volume_uL'), ) elute_min_volume_uL = appcli.param( Key(PresetConfig, 'elute_min_volume_uL'), default=None, ) elute_wait_sec = appcli.param( Key(PresetConfig, 'elute_wait_sec'), default=None, ) elute_spin_sec = appcli.param( Key(PresetConfig, 'elute_spin_sec'), ) group_by = { 'preset': group_by_identity, 'elute_buffer': group_by_identity, 'elute_volume_uL': group_by_identity, } def __init__(self, preset=None): if preset is not None: self.preset = preset def get_protocol(self): p = stepwise.Protocol() pl = stepwise.paragraph_list() ul = stepwise.unordered_list() def break_if_too_long(pl, ul, n=4): if len(ul) > n: ul = stepwise.unordered_list() pl += ul return ul footnotes = [] if self.protocol_link: footnotes.append(self.protocol_link) if self.column_capacity_ug: footnotes.append(f"Column capacity: {self.column_capacity_ug} µg") if self.product_tags and self.show_product_tags: product_tags = oxford_comma(self.product_tags) + ' ' else: product_tags = '' p += pl pl += f"Purify {product_tags}using {self.protocol_name}{p.add_footnotes(*footnotes)}:" pl += ul if self.spin_speed_g: ul += f"Perform all spin steps at {self.spin_speed_g}g." ## Dilute if x := self.target_sample_volume_uL: v = self.sample_volume_uL if not isinstance(x, dict): target = f'{x} µL' skip = v and v == x self.sample_volume_uL = x elif 'min' in x and 'max' in x: target = f"between {x['min']}–{x['max']} µL" skip = v and x['min'] <= v <= x['max'] elif 'min' in x: target = f"at least {x['min']} µL" skip = v and x['min'] <= v elif 'max' in x: target = f"at most {x['max']} µL" skip = v and v <= x['max'] if not skip: ul += f"Ensure that the sample is {target}." ## Bind bind_params = zip_params( self.bind_buffer, self.bind_volume_x, self.bind_volume_uL, ) for bind_buffer, bind_volume_x, bind_volume_uL in bind_params: bind_volume = resolve_volume(bind_volume_uL, bind_volume_x, self.sample_volume_uL) ul += f"Add {bind_volume} {bind_buffer} to the crude {self.sample_type}." if self.ph_buffer: ph_volume = resolve_volume(self.ph_volume_uL, self.ph_volume_x, self.sample_volume_uL) ul += f"If not {self.ph_color}: Add {ph_volume} {self.ph_buffer}." ul += f"Load on a {self.column_name}." ul += flush_column(self.bind_spin_sec, self.bind_vacuum) ul = break_if_too_long(pl, ul) ## Wash wash_params = zip_params( self.wash_buffer, self.wash_volume_uL, self.wash_spin_sec, self.wash_vacuum, ) for wash_buffer, wash_volume_uL, wash_spin_sec, wash_vacuum in wash_params: ul += f"Add {wash_volume_uL} µL {wash_buffer}." ul += flush_column(wash_spin_sec, wash_vacuum) ## Dry if self.dry_spin_sec: ul += flush_column(self.dry_spin_sec) ul = break_if_too_long(pl, ul) ## Elute if self.elute_volume_uL < self.elute_min_volume_uL: warn(f"Elution volume ({self.elute_volume_uL} µL) is below the recommended minimum ({self.elute_min_volume_uL} µL).") ul += f"Add {self.elute_volume_uL} µL {self.elute_buffer}." if self.elute_wait_sec: ul += f"Wait at least {format_sec(self.elute_wait_sec)}." ul += flush_column(self.elute_spin_sec, keep_flowthrough=True) return p
class Gel(Main): """\ Load, run, and stain gels. Usage: gel <preset> <samples> [options] <%! from stepwise_mol_bio import hanging_indent %>\ Arguments: <preset> What kind of gel to run. The following presets are available: ${hanging_indent(app.preset_briefs, 8*' ')} <samples> The names of the samples to run, separated by commas. This can also be a number, which will be taken as the number of samples to run. Options: -p --percent <number> The percentage of polyacrylamide/agarose in the gel being run. -a --additive <str> An extra component to include in the gel itself, e.g. 1x EtBr. -b --buffer <str> The buffer to run the gel in, e.g. TAE. -c --sample-conc <value> The concentration of the sample. This will be used to scale how much sample is mixed with loading buffer, with the goal of mixing the same quantity of material specified in the preset. In order to use this option, the preset must specify a sample concentration. The units of that concentration will be used for this concentration. -v --sample-volume <µL> The volume of sample to mix with loading buffer, in µL. This does not scale the concentration, and may increase or decrease the amount of sample loaded relative to what's specified in the preset. --mix-volume <µL> The volume of the sample/loading buffer mix to prepare for each sample. For example, if you want to run two gels, but the preset only makes enough mix for one, use this option to make more. --mix-extra <percent> How much extra sample/loading buffer mix to make. -M --no-mix Don't describe how to prepare the sample/loading buffer mix. --incubate-temp <°C> What temperature to incubate the sample/loading buffer at before loading it onto the gel. The incubation step will be skipped if neither `--incubate-temp` nor `--incubate-time` are specified (either on the command-line or via the preset). --incubate-time <min> How long to incubate the sample/loading buffer at the specified temperature before loading it onto the gel. The incubation step will be skipped if neither `--incubate-temp` nor `--incubate-time` are specified (either on the command-line or via the preset). --prerun-volts The voltage to pre-run the gel at. By default, this will be the same as the voltage the gel is run at. --prerun-time How long to prerun the gel, in minutes. -l --load-volume <µL> The volume of the sample/loading buffer mix to load onto the gel. --run-volts <V> The voltage to run the gel at. -r --run-time <min> How long to run the gel, in minutes. -s --stain <command> The name (and arguments) of a protocol describing how to stain the gel. For example, this could be 'gelred' or 'coomassie -f'. -S --no-stain Don't describe how to stain/visualize the gel. Configuration: Default values for this protocol can be specified in any of the following stepwise configuration files: ${hanging_indent(app.config_paths, 8)} molbio.gel.presets: Named groups of default reaction parameters. Typically each preset corresponds to a particular kit or protocol. See below for the various settings that can be specified in each preset. molbio.gel.presets.<name>.title: How to briefly describe the gel in the protocol. The default is "electrophoresis". molbio.gel.presets.<name>.inherit: Copy all settings from another preset. This can be used to make small tweaks to a gel protocol, e.g. "SDS PAGE at lower-than-usual voltage". molbio.gel.presets.<name>.sample_mix: A table describing how to prepare the samples for the gel, in the format understood by `stepwise.MasterMix.from_string()`. The table must contain one reagent named "sample". This will be replaced with the actual sample name specified on the command line, if possible. If no table is specified, the sample mix step will be left out of the protocol. molbio.gel.presets.<name>.sample_conc: The default value for the `--sample-conc` option. molbio.gel.presets.<name>.sample_volume_uL: The default value for the `--sample-volume` option. molbio.gel.presets.<name>.ladder: The name of the ladder to use with this gel. molbio.gel.presets.<name>.ladder_volume_uL: How much ladder to load, in µL. molbio.gel.presets.<name>.mix_volume_uL: The default value for the `--mix-volume` option. molbio.gel.presets.<name>.mix_extra_percent: The default value for the `--mix-extra` option. molbio.gel.presets.<name>.incubate_temp_C: The default value for the `--incubate-temp` option. molbio.gel.presets.<name>.incubate_time_min: The default value for the `--incubate-time` option. molbio.gel.presets.<name>.gel_type: What kind of gel to use, e.g. "Bis-Tris/MES SDS PAGE", "TBE/urea PAGE", "TAE/agarose", etc. Don't include the gel percentage here; use the `gel_percent` setting for that. molbio.gel.presets.<name>.gel_percent: The default value for the `--percent` option. molbio.gel.presets.<name>.gel_additive: The default value for the `--additive` option. molbio.gel.presets.<name>.gel_buffer: The default value for the `--buffer` option. molbio.gel.presets.<name>.load_volume_uL: The default value for the `--load-volume` option. molbio.gel.presets.<name>.prerun_volts: The default value for the `--prerun-volts` option. molbio.gel.presets.<name>.prerun_time_min: The default value for the `--prerun-time` option. molbio.gel.presets.<name>.run_volts: The default value for the `--run-volts` option. molbio.gel.presets.<name>.run_time_min: The default value for the `--run-time` option. molbio.gel.presets.<name>.stain: The default value for the `--stain` option. If unspecified, there will be no staining step by default. molbio.gel.presets.<name>.protocol_link: A hyperlink to an online description of the protocol, e.g. from the gel manufacturer. This link will be included as a footnote. """ __config__ = [ DocoptConfig, PresetConfig, StepwiseConfig.setup('molbio.gel'), ] preset_briefs = appcli.config_attr() config_paths = appcli.config_attr() presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param(Key(DocoptConfig, '<preset>'), ) title = appcli.param( Key(PresetConfig, 'title'), default='electrophoresis', ) num_samples = appcli.param( Key(DocoptConfig, '<samples>', cast=parse_num_samples), ignore=None, default=1, ) sample_name = appcli.param( Key(DocoptConfig, '<samples>', cast=parse_sample_name), default=None, ) sample_mix_str = appcli.param( Key(DocoptConfig, '--no-mix', cast=lambda x: None), Key(PresetConfig, 'sample_mix'), default=None, ) sample_conc = appcli.param( Key(DocoptConfig, '--sample-conc'), Key(PresetConfig, 'sample_conc'), cast=float, default=None, ) sample_volume_uL = appcli.param( Key(DocoptConfig, '--sample-volume'), Key(PresetConfig, 'sample_volume_uL'), cast=float, default=None, ) ladder_name = appcli.param( Key(PresetConfig, 'ladder'), default=None, ) ladder_volume_uL = appcli.param(Key(PresetConfig, 'ladder_volume_uL'), ) mix_volume_uL = appcli.param( Key(DocoptConfig, '--mix-volume'), Key(PresetConfig, 'mix_volume_uL'), cast=float, default=None, ) mix_extra_percent = appcli.param( Key(DocoptConfig, '--mix-extra'), Key(PresetConfig, 'mix_extra_percent'), cast=float, default=50, ) incubate_temp_C = appcli.param( Key(DocoptConfig, '--incubate-temp'), Key(PresetConfig, 'incubate_temp_C'), cast=float, ) incubate_time_min = appcli.param( Key(DocoptConfig, '--incubate-time'), Key(PresetConfig, 'incubate_time_min'), cast=int, ) gel_type = appcli.param(Key(PresetConfig, 'gel_type'), ) gel_percent = appcli.param( Key(DocoptConfig, '--percent'), Key(PresetConfig, 'gel_percent'), ) gel_additive = appcli.param( Key(DocoptConfig, '--additive'), Key(PresetConfig, 'gel_additive'), default=None, ) gel_buffer = appcli.param( Key(DocoptConfig, '--buffer'), Key(PresetConfig, 'gel_buffer'), ) load_volume_uL = appcli.param( Key(DocoptConfig, '--load-volume'), Key(PresetConfig, 'load_volume_uL'), cast=float, ) prerun_volts = appcli.param( Key(DocoptConfig, '--prerun-volts'), Key(PresetConfig, 'prerun_volts'), Method(lambda self: self.run_volts), cast=float, ) prerun_time_min = appcli.param( Key(DocoptConfig, '--prerun-time'), Key(PresetConfig, 'prerun_time_min'), cast=int, default=None, ) run_volts = appcli.param( Key(DocoptConfig, '--run-volts'), Key(PresetConfig, 'run_volts'), cast=float, ) run_time_min = appcli.param( Key(DocoptConfig, '--run-time'), Key(PresetConfig, 'run_time_min'), cast=int, ) stain = appcli.param( Key(DocoptConfig, '--stain'), Key(DocoptConfig, '--no-stain', cast=lambda x: None), Key(PresetConfig, 'stain'), default=None, ) protocol_link = appcli.param( Key(PresetConfig, 'protocol_link'), default=None, ) def __init__(self, preset, num_samples=None): self.preset = preset self.num_samples = num_samples def get_protocol(self): p = stepwise.Protocol() if self.sample_mix: p += self.prep_step p += self.run_step if self.stain: p += stepwise.load(self.stain) return p def del_protocol(self): pass def get_prep_step(self): def both_or_neither(key1, key2): has_key1 = has_key2 = True try: value1 = getattr(self, key1) except AttributeError: has_key1 = False try: value2 = getattr(self, key2) except AttributeError: has_key2 = False if has_key1 and not has_key2: raise ConfigError(f"specified {key1!r} but not {key2!r}") if has_key2 and not has_key1: raise ConfigError(f"specified {key2!r} but not {key1!r}") if has_key1 and has_key2: return value1, value2 else: return False s = pl( f"Prepare {plural(self.num_samples):# sample/s} for {self.title}:", self.sample_mix, ) if x := both_or_neither('incubate_temp_C', 'incubate_time_min'): temp_C, time_min = x s += ul(f"Incubate at {temp_C:g}°C for {time_min:g} min.") return s
class Pcr(Main): """\ Amplify a DNA template using polymerase chain reaction (PCR). Usage: pcr <amplicon>... [-a <°C>] [-x <sec> | -l <kb>] [options] pcr (-u <product>) [options] Arguments: <amplicon> The names of the templates and forward/reverse primers to use for each reaction, separated by commas. If a `freezerbox` database is present and these names can be found in it, default values for a number of parameters (e.g. annealing temperate, extension time, etc.) will be derived from it. <%! from stepwise_mol_bio import hanging_indent %>\ Options: -p --preset <name> [default: ${app.preset}] The default reaction and thermocycler parameters to use. The following sets of parameters are currently available: ${hanging_indent(app.preset_briefs, 8*' ')} -u --product <name> The name of a product to make. This name must be present in the FreezerBox database. In this form of the command, all default settings will be taken from the reaction used to synthesize the given product. -l --amplicon-length <bp> The length of the amplicon in base pairs (bp). This can be used to calculate an appropriate extension time. -n --num-reactions <int> [default: ${app.num_reactions}] The number of reactions to set up. -v --reaction-volume <μL> The volume of the PCR reaction. -V --template-volume <µL> The volume of template to use in the reaction. This overrides the value specified by the preset. -T --template-stock <conc> The stock concentration of template to use in the reaction. This overrides the value specified by the preset, without affecting the volume. Include a unit, because none is implied. --primer-stock <µM> The stock concentration of the primers in µM. Both primers must have the same concentration. -y --num-cycles <n> The number of denature/anneal/extend cycles to perform, e.g. 35. --initial-denature-temp <°C> The temperature of the initial denaturation step in °C, e.g. 95. --initial-denature-time <sec> The duration of the initial denaturation step in seconds, e.g. 30. --denature-temp <°C> The temperature of the denaturation step in °C, e.g. 95. --denature-time <sec> The duration of the denaturation step in seconds, e.g. 10. -a --anneal-temp <°C> The temperature of the annealing step in °C, e.g. 60. This is determined by the sequence of the primers. -g --anneal-temp-gradient <range> The range of annealing temperatures that should be tried in a gradient PCR reaction. The range will be centered at the indicated annealing temperature, and the protocol will indicate the corresponding high and low temperatures. --anneal-time <sec> The duration of the annealing step in seconds, e.g. 20. --extend-temp <°C> The temperature of the extension step in °C, e.g. 72. -x --extend-time <sec> The duration of the extension step in seconds. The rule of thumb is 30 sec/kb, perhaps longer if you're amplifying a whole plasmid. --no-round-extend-time When calculating an extension time from a given amplicon length (e.g. `-l`), do *not* round the result to the nearest 30 sec increment. Note that when an explicit extension time is given (e.g. `-x`), it is never rounded. --final-extend-temp <°C> The temperature of the final extension step in °C, e.g. 72. --final-extend-time <sec> The duration of the annealing step in seconds, e.g. 120. --hold-temp <°C> The temperature in °C to hold the reaction at after it completes. --melt-curve-low-temp <°C> The temperature in °C at which to begin recording a melt curve, e.g. 65. This is only relevant for qPCR protocols. --melt-curve-high-temp <°C> The temperature in °C at which to stop recording a melt curve, e.g. 95. This is only relevant for qPCR protocols. --melt-curve-temp-step <°C> How much to increment the temperature in °C at each step in the melt curve, e.g. 0.5°C. This is only relevant for qPCR protocols. --melt-curve-time-step <sec> The duration in seconds of each step in the melt curve, e.g. 5. This is only relevant for qPCR protocols. --two-step Specify that the annealing and extension steps should be combined, e.g. if the primer melting temperatures are very close to the extension temperature or if the amplicon is very short. --qpcr Specify that this is a qPCR protocol, i.e. that fluorescence should be measured after each thermocyler cycle. """ __config__ = [ DocoptConfig.setup(usage_getter=lambda self: self.format_usage()), MakerConfig, PresetConfig, StepwiseConfig.setup('molbio.pcr'), ] usage = appcli.config_attr() preset_briefs = appcli.config_attr() @autoprop class Amplicon(ShareConfigs, Argument): __config__ = [] def _calc_seq(self): try: return find_amplicon( self.template.seq, self.fwd.seq, self.rev.seq, self.template.is_circular, ) except ValueError: err = ConfigError( template=self.template, fwd=self.fwd, rev=self.rev, ) err.brief = "{fwd.tag!r} and {rev.tag!r} do not amplify {template.tag!r}" err.info += "{template.tag}: {template.seq}" err.info += "{fwd.tag}: {fwd.seq}" err.info += "{rev.tag}: {rev.seq}" raise err from None seq = appcli.param( Method(_calc_seq), ) length_bp = appcli.param( Key(DocoptConfig, '--amplicon-length', cast=float), Method(lambda self: len(self.seq)), default=None ) @classmethod def from_tags(cls, template, fwd, rev, **kwargs): return cls( Pcr.Template(template), Pcr.Primer(fwd), Pcr.Primer(rev), **kwargs, ) def __init__(self, template, fwd, rev, **kwargs): self.template = template self.fwd = fwd self.rev = rev self._set_known_attrs(kwargs) def __repr__(self): return f'{self.__class__.__qualname__}({self.template!r}, {self.fwd!r}, {self.rev!r})' def get_reagents(self): return self.template, *self.primers def get_reagent_tags(self): return tuple(x.tag for x in self.reagents) def get_primers(self): return self.fwd, self.rev def set_primers(self, primers): self.fwd, self.rev = primers def on_bind(self, app): super().on_bind(app) for reagent in self.reagents: reagent.bind(app) class Template(Argument): __config__ = [ReagentConfig] seq = appcli.param('seq') is_circular = appcli.param('is_circular') class Primer(ShareConfigs, Argument): __config__ = [ReagentConfig] seq = appcli.param( Key(ReagentConfig, 'seq'), ) melting_temp_C = appcli.param( Key(ReagentConfig, 'melting_temp_C'), ) stock_uM = appcli.param( Key(DocoptConfig, '--primer-stock', cast=float), Key(ReagentConfig, 'conc_uM'), Key(PresetConfig, 'primer_stock_uM'), Key(StepwiseConfig, 'primer_stock_uM'), ) def _calc_anneal_temp_C(self): return [ self.anneal_temp_func( amplicon.fwd.melting_temp_C, amplicon.rev.melting_temp_C, ) for amplicon in self.amplicons ] def _calc_extend_time_s(self): bp = min(bp for x in self.amplicons if (bp := x.length_bp))
class Stain(Main): """\ Stain a gel. Usage: stain <preset> [-t <min>] [-i <cmd> | -I] <%! from stepwise_mol_bio import hanging_indent %>\ Arguments: <preset> The default parameters to use. The following presets are available: ${hanging_indent(app.preset_briefs, 8*' ')} Options: -t --time <min> How long to incubate the gel in the stain, in minutes. -i --imaging-protocol <cmd> -I --no-imaging Don't include the imaging step in the protocol (e.g. so you can provide a customized alternative). """ __config__ = [ DocoptConfig, PresetConfig, StepwiseConfig.setup('molbio.stain'), ] preset_briefs = appcli.config_attr() presets = appcli.param( Key(StepwiseConfig, 'presets'), pick=list, ) preset = appcli.param( Key(DocoptConfig, '<preset>'), ) title = appcli.param( Key(PresetConfig), ) light_sensitive = appcli.param( Key(PresetConfig), default=False, ) prep_buffer = appcli.param( Key(PresetConfig), ) prep_volume_mL = appcli.param( Key(PresetConfig), default=30, ) prep_time_min = appcli.param( Key(PresetConfig), Key(PresetConfig, 'prep_time_hr', cast=lambda x: x*60), ) prep_microwave = appcli.param( Key(PresetConfig), default=False, ) prep_steps = appcli.param( Key(PresetConfig), ) prep_repeats = appcli.param( Key(PresetConfig), default=1, ) stain_buffer = appcli.param( Key(PresetConfig), ) stain_volume_mL = appcli.param( Key(PresetConfig), Key(StepwiseConfig, 'default_stain_volume_mL'), default=30, ) stain_time_min = appcli.param( Key(DocoptConfig, '--time'), Key(PresetConfig), Key(PresetConfig, 'stain_time_hr', cast=lambda x: x*60), ) stain_microwave = appcli.param( Key(PresetConfig), default=False, ) stain_steps = appcli.param( Key(PresetConfig), ) stain_repeats = appcli.param( Key(PresetConfig), default=1, ) stain_rinse_repeats = appcli.param( Key(PresetConfig), default=0, ) destain_buffer = appcli.param( Key(PresetConfig), ) destain_volume_mL = appcli.param( Key(PresetConfig), default=30, ) destain_time_min = appcli.param( Key(PresetConfig), Key(PresetConfig, 'destain_time_hr', cast=lambda x: x*60), ) destain_microwave = appcli.param( Key(PresetConfig), default=False, ) destain_steps = appcli.param( Key(PresetConfig), ) destain_repeats = appcli.param( Key(PresetConfig), default=1, ) imaging_cmd = appcli.param( Key(DocoptConfig, '--imaging-protocol'), Key(DocoptConfig, '--no-imaging', cast=not_), Key(PresetConfig, 'imaging_protocol'), default=None, ) protocol_link = appcli.param( Key(PresetConfig), default=None, ) footnotes = appcli.param( Key(PresetConfig), default_factory=list, ) def get_protocol(self): p = stepwise.Protocol() footnotes = [x] if (x := self.protocol_link) else [] footnotes += self.footnotes s = pl(f"Stain gel with {self.title}{p.add_footnotes(*footnotes)}:") if self.light_sensitive: #s += "Keep gel in the dark in all following steps." s += ul("Keep the stain protected from light.") #s += ul("In all following steps, protect the stain from light.") s += self._format_stage('prep') s += self._format_stage('stain') s += self._format_stage('destain') p += s if self.imaging_cmd: p += stepwise.load(self.imaging_cmd) return p def _format_stage(self, prefix): class SkipStage(Exception): pass def has(attr): return hasattr(self, f'{prefix}_{attr}') def get(attr, *args): return getattr(self, f'{prefix}_{attr}', *args) def have_repeats(): n = get('repeats') return n > 1 if isinstance(n, int) else bool(n) def format_repeats(): n = get('repeats') if isinstance(n, int): n = f'{n}x' return f"Repeat {n}:" def format_submerge(): if not has('buffer'): raise SkipStage return f"Submerge gel in ≈{get('volume_mL', 30)} mL {get('buffer')}." def format_microwave(): if get('microwave', False): return f"Microwave until almost boiling (≈{format_sec(get('microwave_time_s', 45))})." def format_incubate(): return f"Shake gently for {format_min(get('time_min'))}." step_templates = get('steps', ['submerge', 'microwave', 'incubate']) step_formatters = { 'submerge': format_submerge, 'microwave': format_microwave, 'incubate': format_incubate, } out = steps = ul() for template in step_templates: formatter = step_formatters.get(template, lambda: template) try: steps += formatter() except SkipStage: return if have_repeats(): out = ul(pl(format_repeats(), steps, br='\n')) if n := get('rinse_repeats', False): out += f"Rinse {plural(n)://#x }with water." return out