opencloning 0.2.7.2__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opencloning-0.2.7.2 → opencloning-0.2.8}/PKG-INFO +2 -2
- {opencloning-0.2.7.2 → opencloning-0.2.8}/pyproject.toml +2 -2
- opencloning-0.2.8/src/opencloning/cre_lox.py +29 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/dna_utils.py +23 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/assembly.py +45 -7
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/gateway.py +1 -22
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/ncbi_requests.py +4 -2
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/pydantic_models.py +10 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/LICENSE +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/README.md +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/api_config_utils.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/app_settings.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/assembly2.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/barcode.gb +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/common_plasmid.gb +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/example.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/primer_design_settings.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/index.html +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/index.html +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_all.sh +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_clone.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_gather.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_get_primers.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_summary.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/ziqiang_et_al2024/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/dna_functions.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/ebic/__init__.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/ebic/primer_design.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/ebic/primer_design_settings.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/annotation.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/external_import.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/no_assembly.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/no_input.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/other.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/endpoints/primer_design.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/get_router.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/main.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/primer_design.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/request_examples.py +0 -0
- {opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: opencloning
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Manuel Lera-Ramirez
|
|
@@ -15,7 +15,7 @@ Requires-Dist: beautifulsoup4 (>=4.11.1,<5.0.0)
|
|
|
15
15
|
Requires-Dist: biopython (==1.84)
|
|
16
16
|
Requires-Dist: fastapi
|
|
17
17
|
Requires-Dist: httpx (>=0.25.0,<0.26.0)
|
|
18
|
-
Requires-Dist: opencloning-linkml (==0.2.
|
|
18
|
+
Requires-Dist: opencloning-linkml (==0.2.6a0)
|
|
19
19
|
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
20
20
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
21
21
|
Requires-Dist: primer3-py (>=2.0.3,<3.0.0)
|
|
@@ -3,7 +3,7 @@ authors = ["Manuel Lera-Ramirez <manulera14@gmail.com>"]
|
|
|
3
3
|
description = "Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others."
|
|
4
4
|
license = "MIT"
|
|
5
5
|
name = "opencloning"
|
|
6
|
-
version = "v0.2.
|
|
6
|
+
version = "v0.2.8"
|
|
7
7
|
package-mode = true
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
repository = "https://github.com/manulera/OpenCloning_backend"
|
|
@@ -22,7 +22,7 @@ pydantic = "^2.7.1"
|
|
|
22
22
|
pandas = "^2.2.3"
|
|
23
23
|
openpyxl = "^3.1.5"
|
|
24
24
|
pyyaml = "^6.0.2"
|
|
25
|
-
opencloning-linkml = "0.2.
|
|
25
|
+
opencloning-linkml = "0.2.6a0"
|
|
26
26
|
primer3-py = "^2.0.3"
|
|
27
27
|
biopython = "1.84"
|
|
28
28
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
from pydna.dseqrecord import Dseqrecord
|
|
3
|
+
|
|
4
|
+
from .dna_utils import compute_regex_site, dseqrecord_finditer
|
|
5
|
+
|
|
6
|
+
# This is the original loxP sequence, here for reference
|
|
7
|
+
LOXP_SEQUENCE = 'ATAACTTCGTATAGCATACATTATACGAAGTTAT'
|
|
8
|
+
|
|
9
|
+
# This is a consensus sequence, from this Addgene blog post: https://blog.addgene.org/plasmids-101-cre-lox
|
|
10
|
+
# IMPORTANT: Because it is palyndromic, we only look for it in the forward direction, if this was changed
|
|
11
|
+
# to a non-palindromic sequence, you would need to look for matches reversing it, like in Gateway cloning
|
|
12
|
+
LOXP_CONSENSUS = 'ATAACTTCGTATANNNTANNNTATACGAAGTTAT'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
loxP_regex = compute_regex_site(LOXP_CONSENSUS)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cre_loxP_overlap(x: Dseqrecord, y: Dseqrecord, _l: None = None) -> list[tuple[int, int, int]]:
|
|
19
|
+
"""Find matching loxP sites between two sequences."""
|
|
20
|
+
out = list()
|
|
21
|
+
matches_x = dseqrecord_finditer(loxP_regex, x)
|
|
22
|
+
matches_y = dseqrecord_finditer(loxP_regex, y)
|
|
23
|
+
|
|
24
|
+
for match_x, match_y in product(matches_x, matches_y):
|
|
25
|
+
value_x = match_x.group()
|
|
26
|
+
value_y = match_y.group()
|
|
27
|
+
if value_x == value_y:
|
|
28
|
+
out.append((match_x.start(), match_y.start(), len(value_x)))
|
|
29
|
+
return out
|
|
@@ -11,9 +11,15 @@ import os
|
|
|
11
11
|
import shutil
|
|
12
12
|
from pydna.parsers import parse
|
|
13
13
|
from Bio.Align import PairwiseAligner
|
|
14
|
+
from Bio.Data.IUPACData import ambiguous_dna_values as _ambiguous_dna_values
|
|
15
|
+
import re
|
|
14
16
|
|
|
15
17
|
aligner = PairwiseAligner(scoring='blastn')
|
|
16
18
|
|
|
19
|
+
ambiguous_only_dna_values = {**_ambiguous_dna_values}
|
|
20
|
+
for normal_base in 'ACGT':
|
|
21
|
+
del ambiguous_only_dna_values[normal_base]
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
def sum_is_sticky(three_prime_end: tuple[str, str], five_prime_end: tuple[str, str], partial: bool = False) -> int:
|
|
19
25
|
"""Return the overlap length if the 3' end of seq1 and 5' end of seq2 ends are sticky and compatible for ligation.
|
|
@@ -144,3 +150,20 @@ def align_sanger_traces(dseqr: Dseqrecord, sanger_traces: list[str]) -> list[str
|
|
|
144
150
|
sanger_traces = traces_oriented
|
|
145
151
|
|
|
146
152
|
return align_with_mafft([query_str, *sanger_traces], True)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def compute_regex_site(site: str) -> str:
|
|
156
|
+
upper_site = site.upper()
|
|
157
|
+
for k, v in ambiguous_only_dna_values.items():
|
|
158
|
+
if len(v) > 1:
|
|
159
|
+
upper_site = upper_site.replace(k, f"[{''.join(v)}]")
|
|
160
|
+
|
|
161
|
+
# Make case insensitive
|
|
162
|
+
upper_site = f'(?i){upper_site}'
|
|
163
|
+
return upper_site
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def dseqrecord_finditer(pattern: str, seq: Dseqrecord) -> list[re.Match]:
|
|
167
|
+
query = str(seq.seq) if not seq.circular else str(seq.seq) * 2
|
|
168
|
+
matches = re.finditer(pattern, query)
|
|
169
|
+
return (m for m in matches if m.start() <= len(seq))
|
|
@@ -5,7 +5,7 @@ from pydna.primer import Primer as PydnaPrimer
|
|
|
5
5
|
from pydna.crispr import cas9
|
|
6
6
|
from pydantic import conlist, create_model
|
|
7
7
|
from Bio.Restriction.Restriction import RestrictionBatch
|
|
8
|
-
|
|
8
|
+
from opencloning.cre_lox import cre_loxP_overlap
|
|
9
9
|
from ..dna_functions import (
|
|
10
10
|
get_invalid_enzyme_names,
|
|
11
11
|
format_sequence_genbank,
|
|
@@ -24,6 +24,8 @@ from ..pydantic_models import (
|
|
|
24
24
|
AssemblySource,
|
|
25
25
|
OverlapExtensionPCRLigationSource,
|
|
26
26
|
GatewaySource,
|
|
27
|
+
CreLoxRecombinationSource,
|
|
28
|
+
InVivoAssemblySource,
|
|
27
29
|
)
|
|
28
30
|
from ..assembly2 import (
|
|
29
31
|
Assembly,
|
|
@@ -159,6 +161,7 @@ def generate_assemblies(
|
|
|
159
161
|
allow_insertion_assemblies: bool,
|
|
160
162
|
assembly_kwargs: dict | None = None,
|
|
161
163
|
product_callback: Callable[[Dseqrecord], Dseqrecord] = lambda x: x,
|
|
164
|
+
recombination_mode: bool = False,
|
|
162
165
|
) -> dict[Literal['sources', 'sequences'], list[AssemblySource] | list[TextFileSequence]]:
|
|
163
166
|
if assembly_kwargs is None:
|
|
164
167
|
assembly_kwargs = {}
|
|
@@ -175,10 +178,15 @@ def generate_assemblies(
|
|
|
175
178
|
circular_assemblies = asm.get_circular_assemblies()
|
|
176
179
|
out_sources += [create_source(a, True) for a in circular_assemblies]
|
|
177
180
|
if not circular_only:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
if not recombination_mode:
|
|
182
|
+
out_sources += [
|
|
183
|
+
create_source(a, False)
|
|
184
|
+
for a in filter_linear_subassemblies(
|
|
185
|
+
asm.get_linear_assemblies(), circular_assemblies, fragments
|
|
186
|
+
)
|
|
187
|
+
]
|
|
188
|
+
else:
|
|
189
|
+
out_sources += [create_source(a, False) for a in asm.get_insertion_assemblies()]
|
|
182
190
|
else:
|
|
183
191
|
asm = SingleFragmentAssembly(fragments, algorithm=algo, **assembly_kwargs)
|
|
184
192
|
out_sources.extend(create_source(a, True) for a in asm.get_circular_assemblies())
|
|
@@ -385,13 +393,16 @@ async def homologous_recombination(
|
|
|
385
393
|
'/gibson_assembly',
|
|
386
394
|
response_model=create_model(
|
|
387
395
|
'GibsonAssemblyResponse',
|
|
388
|
-
sources=(
|
|
396
|
+
sources=(
|
|
397
|
+
list[Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource, InVivoAssemblySource]],
|
|
398
|
+
...,
|
|
399
|
+
),
|
|
389
400
|
sequences=(list[TextFileSequence], ...),
|
|
390
401
|
),
|
|
391
402
|
)
|
|
392
403
|
async def gibson_assembly(
|
|
393
404
|
sequences: conlist(TextFileSequence, min_length=1),
|
|
394
|
-
source: Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource],
|
|
405
|
+
source: Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource, InVivoAssemblySource],
|
|
395
406
|
minimal_homology: int = Query(
|
|
396
407
|
40, description='The minimum homology between consecutive fragments in the assembly.'
|
|
397
408
|
),
|
|
@@ -522,3 +533,30 @@ async def gateway(
|
|
|
522
533
|
return {'sources': sources, 'sequences': sequences}
|
|
523
534
|
|
|
524
535
|
return resp
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@router.post(
|
|
539
|
+
'/cre_lox_recombination',
|
|
540
|
+
response_model=create_model(
|
|
541
|
+
'CreLoxRecombinationResponse',
|
|
542
|
+
sources=(list[CreLoxRecombinationSource], ...),
|
|
543
|
+
sequences=(list[TextFileSequence], ...),
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
async def cre_lox_recombination(source: CreLoxRecombinationSource, sequences: conlist(TextFileSequence, min_length=1)):
|
|
547
|
+
fragments = [read_dsrecord_from_json(seq) for seq in sequences]
|
|
548
|
+
|
|
549
|
+
# Lambda function for code clarity
|
|
550
|
+
def create_source(a, is_circular):
|
|
551
|
+
return CreLoxRecombinationSource.from_assembly(
|
|
552
|
+
assembly=a, circular=is_circular, id=source.id, fragments=fragments
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
resp = generate_assemblies(
|
|
556
|
+
source, create_source, fragments, False, cre_loxP_overlap, True, recombination_mode=True
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if len(resp['sources']) == 0:
|
|
560
|
+
raise HTTPException(400, 'No compatible Cre/Lox recombination was found.')
|
|
561
|
+
|
|
562
|
+
return resp
|
|
@@ -1,31 +1,10 @@
|
|
|
1
|
-
from Bio.Data.IUPACData import ambiguous_dna_values as _ambiguous_dna_values
|
|
2
1
|
from Bio.Seq import reverse_complement
|
|
3
2
|
from pydna.dseqrecord import Dseqrecord as _Dseqrecord
|
|
4
3
|
import re
|
|
5
4
|
import itertools as _itertools
|
|
6
5
|
from Bio.SeqFeature import SimpleLocation, SeqFeature
|
|
7
6
|
from pydna.utils import shift_location
|
|
8
|
-
|
|
9
|
-
ambiguous_only_dna_values = {**_ambiguous_dna_values}
|
|
10
|
-
for normal_base in 'ACGT':
|
|
11
|
-
del ambiguous_only_dna_values[normal_base]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def compute_regex_site(site: str) -> str:
|
|
15
|
-
upper_site = site.upper()
|
|
16
|
-
for k, v in ambiguous_only_dna_values.items():
|
|
17
|
-
if len(v) > 1:
|
|
18
|
-
upper_site = upper_site.replace(k, f"[{''.join(v)}]")
|
|
19
|
-
|
|
20
|
-
# Make case insensitive
|
|
21
|
-
upper_site = f'(?i){upper_site}'
|
|
22
|
-
return upper_site
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def dseqrecord_finditer(pattern: str, seq: _Dseqrecord) -> list[re.Match]:
|
|
26
|
-
query = str(seq.seq) if not seq.circular else str(seq.seq) * 2
|
|
27
|
-
matches = re.finditer(pattern, query)
|
|
28
|
-
return (m for m in matches if m.start() <= len(seq))
|
|
7
|
+
from .dna_utils import compute_regex_site, dseqrecord_finditer
|
|
29
8
|
|
|
30
9
|
|
|
31
10
|
raw_gateway_common = {
|
|
@@ -32,9 +32,11 @@ async def get_sequence_accessions_from_assembly_accession(assembly_accession: st
|
|
|
32
32
|
resp = await async_get(url, headers=headers)
|
|
33
33
|
data = resp.json()
|
|
34
34
|
if 'reports' in data:
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
refseq_accessions = [report['refseq_accession'] for report in data['reports'] if 'refseq_accession' in report]
|
|
36
|
+
genbank_accessions = [
|
|
37
|
+
report['genbank_accession'] for report in data['reports'] if 'genbank_accession' in report
|
|
37
38
|
]
|
|
39
|
+
return refseq_accessions + genbank_accessions
|
|
38
40
|
elif 'total_count' in data:
|
|
39
41
|
raise HTTPException(400, f'No sequence accessions linked, see {url}')
|
|
40
42
|
else:
|
|
@@ -45,6 +45,8 @@ from opencloning_linkml.datamodel import (
|
|
|
45
45
|
IGEMSource as _IGEMSource,
|
|
46
46
|
ReverseComplementSource as _ReverseComplementSource,
|
|
47
47
|
SEVASource as _SEVASource,
|
|
48
|
+
CreLoxRecombinationSource as _CreLoxRecombinationSource,
|
|
49
|
+
InVivoAssemblySource as _InVivoAssemblySource,
|
|
48
50
|
)
|
|
49
51
|
from pydna.utils import shift_location as _shift_location
|
|
50
52
|
from .assembly2 import edge_representation2subfragment_representation, subfragment_representation2edge_representation
|
|
@@ -338,6 +340,10 @@ class InFusionSource(AssemblySourceCommonClass, _InFusionSource):
|
|
|
338
340
|
pass
|
|
339
341
|
|
|
340
342
|
|
|
343
|
+
class InVivoAssemblySource(AssemblySourceCommonClass, _InVivoAssemblySource):
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
|
|
341
347
|
class CRISPRSource(AssemblySourceCommonClass, _CRISPRSource):
|
|
342
348
|
|
|
343
349
|
# TODO
|
|
@@ -384,6 +390,10 @@ class GatewaySource(AssemblySourceCommonClass, _GatewaySource):
|
|
|
384
390
|
return super().from_assembly(assembly, id, circular, fragments, reaction_type=reaction_type)
|
|
385
391
|
|
|
386
392
|
|
|
393
|
+
class CreLoxRecombinationSource(AssemblySourceCommonClass, _CreLoxRecombinationSource):
|
|
394
|
+
pass
|
|
395
|
+
|
|
396
|
+
|
|
387
397
|
class OligoHybridizationSource(SourceCommonClass, _OligoHybridizationSource):
|
|
388
398
|
pass
|
|
389
399
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/EBIC/common_plasmid.gb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_clone.py
RENAMED
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_gather.py
RENAMED
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_get_primers.py
RENAMED
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/pombe/pombe_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{opencloning-0.2.7.2 → opencloning-0.2.8}/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|