From fb0ea4fce6fb6f33bbd5c82c2bb8c5c4f5dc3e28 Mon Sep 17 00:00:00 2001 From: David Doty Date: Tue, 2 Aug 2022 10:22:26 -0700 Subject: [PATCH] testing whether forward annotations work on Python 3.7 --- nuad/constraints.py | 86 +++++++++++++++++++++---------------------- nuad/search.py | 79 ++++++++++++++++++++------------------- nuad/vienna_nupack.py | 12 +++--- 3 files changed, 89 insertions(+), 88 deletions(-) diff --git a/nuad/constraints.py b/nuad/constraints.py index ab7cde54..84441534 100644 --- a/nuad/constraints.py +++ b/nuad/constraints.py @@ -23,8 +23,8 @@ import math import json from decimal import Decimal -from typing import List, Set, Optional, Dict, Callable, Iterable, Tuple, Union, Collection, TypeVar, Any, \ - cast, Generic, DefaultDict, FrozenSet, Iterator, Sequence, Type +from typing import List, Set, Dict, Callable, Iterable, Tuple, Collection, TypeVar, Any, \ + cast, Generic, DefaultDict, FrozenSet, Iterator, Sequence, Type, Union, Optional from dataclasses import dataclass, field, InitVar from abc import ABC, abstractmethod from collections import defaultdict @@ -498,12 +498,12 @@ class BaseCountConstraint(NumpyConstraint): base: str """Base to count.""" - high_count: Optional[int] = None + high_count: int | None = None """ Count of :py:data:`BaseCountConstraint.base` must be at most :py:data:`BaseCountConstraint.high_count`. """ - low_count: Optional[int] = None + low_count: int | None = None """ Count of :py:data:`BaseCountConstraint.base` must be at least :py:data:`BaseCountConstraint.low_count`. """ @@ -599,7 +599,7 @@ class BaseAtPositionConstraint(NumpyConstraint): can only be placed on an T. """ - bases: Union[str, Collection[str]] + bases: str | Collection[str] """ Base(s) to require at position :py:data:`BasePositionConstraint.position`. @@ -639,7 +639,7 @@ class ForbiddenSubstringConstraint(NumpyConstraint): Restricts the sequence not to contain a certain substring(s), e.g., GGGG. """ - substrings: Union[str, Collection[str]] + substrings: str | Collection[str] """ Substring(s) to forbid. @@ -647,7 +647,7 @@ class ForbiddenSubstringConstraint(NumpyConstraint): If a collection, all substrings must have the same length. """ - indices: Optional[Sequence[int]] = None + indices: Sequence[int] | None = None """ Indices at which to check for each substring in :data:`ForbiddenSubstringConstraint.substrings`. If not specified, all appropriate indices are checked. @@ -725,7 +725,7 @@ class RunsOfBasesConstraint(NumpyConstraint): length: int """Length of run to forbid.""" - def __init__(self, bases: Union[str, Collection[str]], length: int): + def __init__(self, bases: str | Collection[str], length: int) -> None: """ :param bases: Can either be a single base, or a collection (e.g., list, tuple, set). :param length: length of run to forbid @@ -809,8 +809,8 @@ class SubstringSampler(JSONSerializable): Computed in constructor from other arguments.""" def __init__(self, supersequence: str, substring_length: int, - except_start_indices: Optional[Iterable[int]] = None, - except_overlapping_indices: Optional[Iterable[int]] = None, + except_start_indices: Iterable[int] | None = None, + except_overlapping_indices: Iterable[int] | None = None, circular: bool = False, ) -> None: if except_start_indices is not None and except_overlapping_indices is not None: @@ -914,12 +914,12 @@ class DomainPool(JSONSerializable): name: str """Name of this :any:`DomainPool`. Must be unique.""" - length: Optional[int] = None + length: int | None = None """Length of DNA sequences generated by this :any:`DomainPool`. Should be None if :data:`DomainPool.possible_sequences` is specified.""" - possible_sequences: Union[None, List[str], SubstringSampler] = None + possible_sequences: List[str] | SubstringSampler | None = None """ If specified, all other fields except :data:`DomainPool.name` and :data:`DomainPool.length` are ignored. @@ -1113,7 +1113,7 @@ def from_json_serializable(json_map: Dict[str, Any]) -> DomainPool: possible_sequences=possible_sequences, ) - def _first_sequence_satisfying_sequence_constraints(self, seqs: nn.DNASeqList) -> Optional[str]: + def _first_sequence_satisfying_sequence_constraints(self, seqs: nn.DNASeqList) -> str | None: if len(seqs) == 0: return None if len(self.sequence_constraints) == 0: @@ -1133,7 +1133,7 @@ def satisfies_sequence_constraints(self, sequence: str) -> bool: """ return all(constraint(sequence) for constraint in self.sequence_constraints) - def generate_sequence(self, rng: np.random.Generator, previous_sequence: Optional[str] = None) -> str: + def generate_sequence(self, rng: np.random.Generator, previous_sequence: str | None = None) -> str: """ Returns a DNA sequence of given length satisfying :py:data:`DomainPool.numpy_constraints` and :py:data:`DomainPool.sequence_constraints` @@ -1199,7 +1199,7 @@ def _sample_hamming_distance_from_sequence(self, previous_sequence: str, rng: np existing_hamming_probabilities /= prob_sum sampled_distance: int = rng.choice(available_distances_arr, p=existing_hamming_probabilities) - sequence: Optional[str] = None + sequence: str | None = None while sequence is None: # each iteration of this loop tries one value of num_to_generate bases = self._bases_to_use() @@ -1383,7 +1383,7 @@ def fixed(self) -> bool: pass @abstractmethod - def individual_parts(self) -> Union[Tuple[Domain, ...], Tuple[Strand, ...]]: + def individual_parts(self) -> Tuple[Domain, ...] | Tuple[Strand, ...]: # if Part represents a tuple, e.g., StrandPair or DomainPair, then returns tuple of # individual domains/strands pass @@ -1415,7 +1415,7 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): _starred_name: str - _pool: Optional[DomainPool] = field(init=False, default=None, compare=False, hash=False) + _pool: DomainPool | None = field(init=False, default=None, compare=False, hash=False) """ Each :any:`Domain` in the same :any:`DomainPool` as this one share a set of properties, such as length and individual :any:`DomainConstraint`'s. @@ -1426,7 +1426,7 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): # - if parent is not none, make recursive call to set_sequence_recursive_up # TODO: `set_sequence_recursive_down` # - iterate over children, call set_sequence - _sequence: Optional[str] = field(init=False, repr=False, default=None, compare=False, hash=False) + _sequence: str | None = field(init=False, repr=False, default=None, compare=False, hash=False) """ DNA sequence assigned to this :any:`Domain`. This is assumed to be the sequence of the unstarred variant; the starred variant has the Watson-Crick complement, @@ -1454,7 +1454,7 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): Note: If a domain is fixed then all of its subdomains must also be fixed. """ - label: Optional[DomainLabel] = None + label: DomainLabel | None = None """ Optional generic "label" object to associate to this :any:`Domain`. @@ -1483,7 +1483,7 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): so the domains are dependent on the root domain's assigned sequence. """ - length: Optional[int] = None + length: int | None = None """ Length of this domain. If None, then the method :meth:`Domain.get_length` asks :data:`Domain.pool` for the length. However, a :any:`Domain` with :data:`Domain.dependent` set to True has no @@ -1494,14 +1494,14 @@ class Domain(Part, JSONSerializable, Generic[DomainLabel]): """List of smaller subdomains whose concatenation is this domain. If empty, then there are no subdomains. """ - parent: Optional[Domain] = field(init=False, default=None) + parent: Domain | None = field(init=False, default=None) """Domain of which this is a subdomain. Note, this is not set manually, this is set by the library based on the :data:`Domain.subdomains` of other domains in the same tree. """ - def __init__(self, name: str, pool: Optional[DomainPool] = None, sequence: Optional[str] = None, - fixed: bool = False, label: Optional[DomainLabel] = None, dependent: bool = False, - subdomains: Optional[List["Domain"]] = None, weight: Optional[float] = None) -> None: + def __init__(self, name: str, pool: DomainPool | None = None, sequence: str | None = None, + fixed: bool = False, label: DomainLabel | None = None, dependent: bool = False, + subdomains: List[Domain] | None = None, weight: float | None = None) -> None: if subdomains is None: subdomains = [] self._name = name @@ -1559,7 +1559,7 @@ def __repr__(self) -> str: def individual_parts(self) -> Tuple[Domain, ...]: return self, - def to_json_serializable(self, suppress_indent: bool = True) -> Union[NoIndent, Dict[str, Any]]: + def to_json_serializable(self, suppress_indent: bool = True) -> NoIndent | Dict[str, Any]: """ :return: Dictionary ``d`` representing this :any:`Domain` that is "naturally" JSON serializable, @@ -1578,7 +1578,7 @@ def to_json_serializable(self, suppress_indent: bool = True) -> Union[NoIndent, @staticmethod def from_json_serializable(json_map: Dict[str, Any], - pool_with_name: Optional[Dict[str, DomainPool]], + pool_with_name: Dict[str, DomainPool] | None, label_decoder: Callable[[Any], DomainLabel] = lambda label: label) \ -> Domain[DomainLabel]: """ @@ -1597,14 +1597,14 @@ def from_json_serializable(json_map: Dict[str, Any], :py:meth:`Domain.to_json_serializable`. """ name: str = mandatory_field(Domain, json_map, name_key) - sequence: Optional[str] = json_map.get(sequence_key) + sequence: str | None = json_map.get(sequence_key) fixed: bool = json_map.get(fixed_key, False) label_json: Any = json_map.get(label_key) label = label_decoder(label_json) - pool: Optional[DomainPool] - pool_name: Optional[str] = json_map.get(domain_pool_name_key) + pool: DomainPool | None + pool_name: str | None = json_map.get(domain_pool_name_key) if pool_name is not None: if pool_with_name is not None: pool = pool_with_name[pool_name] if pool_with_name is not None else None @@ -1787,7 +1787,7 @@ def _set_parent_sequence(self, new_sequence: str) -> None: # Add up lengths of subdomains, add new_sequence idx = 0 assert self in parent._subdomains - sd: Optional[Domain] = None + sd: Domain | None = None for sd in parent._subdomains: if sd == self: break @@ -1936,7 +1936,7 @@ def _check_exactly_one_independent_subdomain_all_paths(self) -> None: f"Domain {self} is dependent and could not find exactly one independent subdomain " f"in subdomain graph rooted at subdomain {sd}. The following error was found: {e}") - def _check_acyclic_subdomain_graph(self, seen_domains: Optional[Set["Domain"]] = None) -> None: + def _check_acyclic_subdomain_graph(self, seen_domains: Set["Domain"] | None = None) -> None: """Check to see if domain's subdomain graph contains a cycle. :param seen_domains: All the domains seen so far (used by implementation) @@ -2004,8 +2004,8 @@ def _get_all_domains_from_parent(self) -> List["Domain"]: return domains - def _get_all_domains_from_this_subtree(self, excluded_subdomain: Optional['Domain'] = None) \ - -> List["Domain"]: + def _get_all_domains_from_this_subtree(self, excluded_subdomain: Domain | None = None) \ + -> List[Domain]: # includes itself domains = [self] for sd in self._subdomains: @@ -2201,7 +2201,7 @@ def __post_init__(self) -> None: f'IDTFields.plate = {self.plate}') def to_json_serializable(self, suppress_indent: bool = True, - **kwargs: Any) -> Union[NoIndent, Dict[str, Any]]: + **kwargs: Any) -> NoIndent | Dict[str, Any]: dct: Dict[str, Any] = dict(self.__dict__) if self.plate is None: del dct['plate'] @@ -2511,7 +2511,7 @@ def _ensure_modifications_legal(self, check_offsets_legal: bool = False) -> None f"{len(self.sequence(delimiter=''))}: " f"{self.modifications_int}") - def to_json_serializable(self, suppress_indent: bool = True) -> Union[NoIndent, Dict[str, Any]]: + def to_json_serializable(self, suppress_indent: bool = True) -> NoIndent | Dict[str, Any]: """ :return: Dictionary ``d`` representing this :any:`Strand` that is "naturally" JSON serializable, @@ -4220,8 +4220,8 @@ class Result(Generic[DesignPart]): def __init__(self, excess: float, - summary: Optional[str] = None, - value: Optional[Union[float, str, pint.Quantity[Decimal]]] = None) -> None: + summary: str | None = None, + value: float | str | pint.Quantity[Decimal] | None = None) -> None: self.excess = excess if summary is None: if value is None: @@ -4236,7 +4236,7 @@ def __init__(self, self.part = None # type:ignore -def parse_and_normalize_quantity(quantity: Union[float, int, str, pint.Quantity]) \ +def parse_and_normalize_quantity(quantity: float | int | str | pint.Quantity) \ -> pint.Quantity[Decimal]: if isinstance(quantity, (str, float, int)): quantity = ureg(quantity) @@ -4665,7 +4665,7 @@ def verify_designs_match(design1: Design, design2: Design, check_fixed: bool = T f'design2 domain {domain2.name} pool = {domain2.pool.name}') -def convert_threshold(threshold: Union[float, Dict[T, float]], key: T) -> float: +def convert_threshold(threshold: float | Dict[T, float], key: T) -> float: """ :param threshold: either a single float, or a dictionary mapping instances of T to floats :param key: instance of T @@ -5014,7 +5014,7 @@ def nupack_strand_pair_constraint_by_number_matching_domains( descriptions = { num_matching: _pair_default_description('strand', 'NUPACK', threshold, temperature) + f'\nfor strands with {num_matching} complementary ' - f'{"domain" if num_matching==1 else "domains"}' + f'{"domain" if num_matching == 1 else "domains"}' for num_matching, threshold in thresholds.items() } @@ -5454,7 +5454,7 @@ def rna_cofold_strand_pairs_constraints_by_number_matching_domains( descriptions = { num_matching: _pair_default_description('strand', 'RNAcofold', threshold, temperature) + f'\nfor strands with {num_matching} complementary ' - f'{"domain" if num_matching==1 else "domains"}' + f'{"domain" if num_matching == 1 else "domains"}' for num_matching, threshold in thresholds.items() } return _strand_pairs_constraints_by_number_matching_domains( @@ -5528,7 +5528,7 @@ def rna_duplex_strand_pairs_constraints_by_number_matching_domains( descriptions = { num_matching: _pair_default_description('strand', 'RNAduplex', threshold, temperature) + f'\nfor strands with {num_matching} complementary ' - f'{"domain" if num_matching==1 else "domains"}' + f'{"domain" if num_matching == 1 else "domains"}' for num_matching, threshold in thresholds.items() } @@ -5645,7 +5645,7 @@ def energy_excess(energy: float, threshold: float) -> float: def energy_excess_domains(energy: float, - threshold: Union[float, Dict[Tuple[DomainPool, DomainPool], float]], + threshold: float | Dict[Tuple[DomainPool, DomainPool], float], domain1: Domain, domain2: Domain) -> float: threshold_value = 0.0 # noqa; warns that variable isn't used even though it clearly is if isinstance(threshold, Number): diff --git a/nuad/search.py b/nuad/search.py index f1f29572..22617729 100644 --- a/nuad/search.py +++ b/nuad/search.py @@ -20,8 +20,8 @@ from collections import defaultdict, deque import collections.abc as abc from dataclasses import dataclass, field -from typing import List, Tuple, FrozenSet, Optional, Dict, Callable, Iterable, \ - Deque, TypeVar, Union, Generic, Iterator, Any +from typing import List, Tuple, FrozenSet, Dict, Callable, Iterable, \ + Deque, TypeVar, Generic, Iterator, Any import statistics import textwrap import re @@ -75,7 +75,7 @@ def new_process_pool(cpu_count: int) -> pathos.pools.ProcessPool: return pathos.pools.ProcessPool(processes=cpu_count) -_process_pool: Optional[pathos.pools.ProcessPool] = None +_process_pool: None | pathos.pools.ProcessPool = None log_names_of_domains_and_strands_checked = False pprint_indent = 4 @@ -89,7 +89,7 @@ def default_output_directory() -> str: # combinations of inputs so it's worth it to maintain a cache. @lru_cache() def find_parts_to_check(constraint: nc.Constraint[DesignPart], design: nc.Design, - domains_changed: Optional[Tuple[Domain]]) -> Tuple[DesignPart, ...]: + domains_changed: None | Tuple[Domain]) -> Tuple[DesignPart, ...]: if domains_changed is not None: domains_changed_full: OrderedSet[Domain] = OrderedSet(domains_changed) for domain in domains_changed: @@ -147,7 +147,7 @@ def _at_least_one_domain_unfixed(pair: Tuple[Domain, Domain]) -> bool: def _determine_domains_to_check(design: Design, - domains_changed: Optional[Tuple[Domain]], + domains_changed: None | Tuple[Domain], constraint: ConstraintWithDomains) -> Tuple[Domain, ...]: """ Determines domains to check in `all_domains`. @@ -169,7 +169,7 @@ def _determine_domains_to_check(design: Design, def _determine_strands_to_check(design: Design, - domains_changed: Optional[Tuple[Domain]], + domains_changed: None | Tuple[Domain], constraint: ConstraintWithStrands) -> Tuple[Strand, ...]: """ Similar to _determine_domains_to_check but for strands. @@ -193,7 +193,7 @@ def _determine_strands_to_check(design: Design, def _determine_domain_pairs_to_check(design: Design, - domains_changed: Optional[Tuple[Domain]], + domains_changed: None | Tuple[Domain], constraint: ConstraintWithDomainPairs) -> Tuple[DomainPair, ...]: """ Determines domain pairs to check between domains in `all_domains`. @@ -238,7 +238,7 @@ def _at_least_one_strand_unfixed(pair: Tuple[Strand, Strand]) -> bool: def _determine_strand_pairs_to_check(design: Design, - domains_changed: Optional[Tuple[Domain]], + domains_changed: None | Tuple[Domain], constraint: ConstraintWithStrandPairs) -> Tuple[StrandPair, ...]: # Similar to _determine_domain_pairs_to_check but for strands. # some code is repeated here, but otherwise it's way too slow on a large design to iterate over @@ -272,7 +272,7 @@ def _determine_strand_pairs_to_check(design: Design, return strand_pairs_to_check -def _determine_complexes_to_check(domains_changed: Optional[Iterable[Domain]], +def _determine_complexes_to_check(domains_changed: Iterable[Domain] | None, constraint: ConstraintWithComplexes) -> Tuple[Complex, ...]: """ Similar to _determine_domain_pairs_to_check but for complexes. @@ -298,7 +298,7 @@ def _determine_complexes_to_check(domains_changed: Optional[Iterable[Domain]], return tuple(complexes_to_check) -def _strands_containing_domains(domains: Optional[Iterable[Domain]], strands: List[Strand]) -> List[Strand]: +def _strands_containing_domains(domains: Iterable[Domain] | None, strands: List[Strand]) -> List[Strand]: """ :param domains: :any:`Domain`'s to check for, or None to return all of `strands` @@ -362,7 +362,7 @@ def _independent_domains_in_part(part: DesignPart, exclude_fixed: bool) -> Tuple T = TypeVar('T') -def remove_none_from_list(lst: Iterable[Optional[T]]) -> List[T]: +def remove_none_from_list(lst: Iterable[T | None]) -> List[T]: return [elt for elt in lst if elt is not None] @@ -436,7 +436,7 @@ def _write_report(params: SearchParameters, directories: _Directories, def _write_text_intermediate_and_final_files(content: str, best_filename: str, - idx_filename: Optional[str]) -> None: + idx_filename: str | None) -> None: with open(best_filename, 'w') as file: file.write(content) if idx_filename is not None: @@ -507,8 +507,8 @@ class _Directories: sequences_filename_no_ext: str = field(init=False, default='sequences') report_filename_no_ext: str = field(init=False, default='report') - debug_file_handler: Optional[logging.FileHandler] = field(init=False, default=None) - info_file_handler: Optional[logging.FileHandler] = field(init=False, default=None) + debug_file_handler: logging.FileHandler | None = field(init=False, default=None) + info_file_handler: logging.FileHandler | None = field(init=False, default=None) def all_subdirectories(self, params: SearchParameters) -> List[str]: result = [] @@ -538,7 +538,7 @@ def __init__(self, out: str, debug: bool, info: bool) -> None: nc.logger.addHandler(self.info_file_handler) @staticmethod - def indexed_full_filename_noext(filename_no_ext: str, directory: str, idx: Union[int, str], + def indexed_full_filename_noext(filename_no_ext: str, directory: str, idx: int | str, ext: str) -> str: relative_filename = f'{filename_no_ext}-{idx}.{ext}' full_filename = os.path.join(directory, relative_filename) @@ -549,16 +549,16 @@ def best_full_filename_noext(self, filename_no_ext: str, ext: str) -> str: full_filename = os.path.join(self.out, relative_filename) return full_filename - def indexed_design_full_filename_noext(self, idx: Union[int, str]) -> str: + def indexed_design_full_filename_noext(self, idx: int | str) -> str: return self.indexed_full_filename_noext(self.design_filename_no_ext, self.design, idx, 'json') - def indexed_rng_full_filename_noext(self, idx: Union[int, str]) -> str: + def indexed_rng_full_filename_noext(self, idx: int | str) -> str: return self.indexed_full_filename_noext(self.rng_state_filename_no_ext, self.rng_state, idx, 'json') - def indexed_sequences_full_filename_noext(self, idx: Union[int, str]) -> str: + def indexed_sequences_full_filename_noext(self, idx: int | str) -> str: return self.indexed_full_filename_noext(self.sequences_filename_no_ext, self.sequence, idx, 'txt') - def indexed_report_full_filename_noext(self, idx: Union[int, str]) -> str: + def indexed_report_full_filename_noext(self, idx: int | str) -> str: return self.indexed_full_filename_noext(self.report_filename_no_ext, self.report, idx, 'txt') def best_design_full_filename_noext(self) -> str: @@ -609,7 +609,7 @@ class SearchParameters: List of :any:`constraints.Constraint`'s to apply to the :any:`Design`. """ - probability_of_keeping_change: Optional[Callable[[float], float]] = None + probability_of_keeping_change: Callable[[float], float] | None = None """ Function giving the probability of keeping a change in one :any:`Domain`'s DNA sequence, if the new sequence affects the total score of all violated @@ -618,13 +618,13 @@ class SearchParameters: behavior if this parameter is not specified. """ - random_seed: Optional[int] = None + random_seed: int | None = None """ Integer given as a random seed to the numpy random number generator, used for all random choices in the algorithm. Set this to a fixed value to allow reproducibility. """ - never_increase_score: Optional[bool] = None + never_increase_score: bool | None = None """ If specified and True, then it is assumed that the function probability_of_keeping_change returns 0 for any negative value of `score_delta` (i.e., the search @@ -639,7 +639,7 @@ class SearchParameters: goes uphill; the optimization will essentially prevent most uphill climbs from occurring. """ - out_directory: Optional[str] = None + out_directory: str | None = None """ Directory in which to write output files (report on constraint violations and DNA sequences) whenever a new optimal sequence assignment is found. @@ -682,12 +682,12 @@ class SearchParameters: and summary of all constraint checks of a certain type (e.g., how many constraint checks there were). """ - max_iterations: Optional[int] = None + max_iterations: int | None = None """ Maximum number of iterations of search to perform. """ - target_score: Optional[float] = None + target_score: float | None = None """ Total violation score to attempt to obtain. A score of 0.0 represents that all constraints are satisfied. Often a search can get very close to score 0.0 quickly, but take a very long time @@ -703,7 +703,7 @@ class SearchParameters: that they violated. """ - num_digits_update: Optional[int] = None + num_digits_update: int | None = None """ Number of digits to use when writing update number in filenames. By default, they will be written using just enough digits for each integer, @@ -1322,7 +1322,6 @@ def _log_constraint_summary(*, params: SearchParameters, print() print(header) - def _dec(score_: float) -> int: # how many decimals after decimal point to use given the score return max(1, math.ceil(math.log(1 / score_, 10)) + 2) if score_ > 0 else 1 @@ -1637,7 +1636,7 @@ def sum_domain_scores(domain_to_violations: Dict[Domain, List[Evaluation]]) -> D } return domain_to_score - def calculate_score_gap(self, fixed: Optional[bool] = None) -> Optional[float]: + def calculate_score_gap(self, fixed: bool | None = None) -> float | None: # fixed is None (all violations), True (fixed violations), or False (nonfixed violations) # total score of evaluations - total score of new evaluations assert len(self.evaluations) > 0 @@ -1696,8 +1695,8 @@ def call_evaluate_sequential(partz: Tuple[nc.DesignPart]) -> List[Tuple[nc.Desig def evaluate_constraint(self, constraint: Constraint[DesignPart], design: Design, # only used with DesignConstraint - score_gap: Optional[float], - domains_new: Optional[Tuple[Domain]], + score_gap: float | None, + domains_new: Tuple[Domain] | None, ) -> float: # returns score gap = score(old evals) - score(new evals); # if gap > 0, then new evals haven't added up to @@ -1838,7 +1837,7 @@ def update_scores_and_counts(self) -> None: self.total_score_nonfixed += violation.score self.num_violations_nonfixed += 1 - def total_score_new(self, fixed: Optional[bool] = None) -> float: + def total_score_new(self, fixed: bool | None = None) -> float: # return total score of all evaluations, or only fixed, or only nonfixed if fixed is None: total_score_old = self.total_score @@ -2111,7 +2110,7 @@ def summary_of_constraints(constraints: Iterable[Constraint], report_only_violat ''' + summary -def _value_from_constraint_dict(dct: Union[V, Dict[Union[str, Constraint], V]], +def _value_from_constraint_dict(dct: V | Dict[str | Constraint, V], constraint: Constraint, default_value: V, klass: type) -> V: # useful for many parameters in display_report, # where they can either be a single value to use for all constraints, @@ -2141,14 +2140,14 @@ def _value_from_constraint_dict(dct: Union[V, Dict[Union[str, Constraint], V]], def display_report(design: nc.Design, constraints: Iterable[Constraint], report_only_violations: bool = False, layout: Literal['horz', 'vert'] = 'vert', - xlims: Union[Optional[Tuple[float, float]], - Dict[Union[str, Constraint], Optional[Tuple[float, float]]]] = None, - ylims: Union[Optional[Tuple[float, float]], - Dict[Union[str, Constraint], Optional[Tuple[float, float]]]] = None, - yscales: Union[Literal['log', 'linear', 'symlog'], - Dict[Union[str, Constraint], - Literal['log', 'linear', 'symlog']]] = _default_yscale, - bins: Union[int, Dict[Union[str, Constraint], int]] = _default_num_bins) -> None: + xlims: None | Tuple[float, float] | + Dict[str | Constraint, None | Tuple[float, float]] = None, + ylims: None | Tuple[float, float] | + Dict[str | Constraint, None | Tuple[float, float]] = None, + yscales: Literal['log', 'linear', 'symlog'] | + Dict[str | Constraint, + Literal['log', 'linear', 'symlog']] = _default_yscale, + bins: int | Dict[str | Constraint, int] = _default_num_bins) -> None: """ When run in a Jupyter notebook cell, creates a :any:`ConstraintsReport` (the one returned from :meth:`create_report`) and displays its data graphically in the notebook using matplotlib. diff --git a/nuad/vienna_nupack.py b/nuad/vienna_nupack.py index 0dc04f4d..ae876e4c 100644 --- a/nuad/vienna_nupack.py +++ b/nuad/vienna_nupack.py @@ -9,6 +9,8 @@ that is much faster than calling :meth:`pfunc` on the same pair of strands). """ # noqa +from __future__ import annotations + import math import itertools import os @@ -18,7 +20,7 @@ import sys from multiprocessing.pool import ThreadPool from pathos.pools import ProcessPool -from typing import Sequence, Union, Tuple, List, Optional, Iterable +from typing import Sequence, Tuple, List, Iterable import numpy as np @@ -64,7 +66,7 @@ def calculate_strand_association_penalty(temperature: float, num_seqs: int) -> f return adjust * (num_seqs - 1) -def pfunc(seqs: Union[str, Tuple[str, ...]], +def pfunc(seqs: str | Tuple[str, ...], temperature: float = default_temperature, sodium: float = default_sodium, magnesium: float = default_magnesium, @@ -131,13 +133,13 @@ def pfunc(seqs: Union[str, Tuple[str, ...]], return dg -def tupleize(seqs: Union[str, Iterable[str]]) -> Tuple[str, ...]: +def tupleize(seqs: str | Iterable[str]) -> Tuple[str, ...]: return (seqs,) if isinstance(seqs, str) else tuple(seqs) def pfunc_parallel( pool: ProcessPool, - all_seqs: Sequence[Union[str, Tuple[str, ...]]], + all_seqs: Sequence[str | Tuple[str, ...]], temperature: float = default_temperature, sodium: float = default_sodium, magnesium: float = default_magnesium, @@ -257,7 +259,7 @@ def call_subprocess(command_strs: List[str], user_input: str) -> Tuple[str, str] # Passing either of the keyword arguments universal_newlines=True or encoding='utf8' # solves the problem for python3.6. For python3.7 (but not 3.6) one can use text=True # XXX: Then why are none of those keyword arguments being used here?? - process: Optional[sub.Popen] = None + process: sub.Popen | None = None command_strs = (['wsl.exe', '-e'] if os_is_windows else []) + command_strs try: with sub.Popen(command_strs, stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE) as process: