Skip to content

Commit

Permalink
testing whether forward annotations work on Python 3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
dave-doty committed Aug 2, 2022
1 parent a253254 commit fb0ea4f
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 88 deletions.
86 changes: 43 additions & 43 deletions nuad/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
"""
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -639,15 +639,15 @@ 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.
Can either be a single substring, or a collection (e.g., list, tuple, set).
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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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`
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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]:
"""
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit fb0ea4f

Please sign in to comment.