scout-browser 4.88.1__py3-none-any.whl → 4.89__py3-none-any.whl

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.
Files changed (59) hide show
  1. scout/__version__.py +1 -1
  2. scout/adapter/mongo/base.py +1 -1
  3. scout/adapter/mongo/case.py +185 -117
  4. scout/adapter/mongo/omics_variant.py +19 -0
  5. scout/build/case.py +1 -0
  6. scout/commands/load/variants.py +121 -40
  7. scout/commands/update/case.py +56 -10
  8. scout/constants/case_tags.py +5 -0
  9. scout/constants/disease_parsing.py +2 -2
  10. scout/constants/igv_tracks.py +2 -2
  11. scout/constants/indexes.py +8 -1
  12. scout/demo/643594.config.yaml +1 -0
  13. scout/demo/panel_1.txt +2 -0
  14. scout/demo/resources/ensembl_exons_37_reduced.txt +135 -0
  15. scout/demo/resources/ensembl_exons_38_reduced.txt +166 -0
  16. scout/demo/resources/ensembl_genes_37_reduced.txt +2 -0
  17. scout/demo/resources/ensembl_genes_38_reduced.txt +2 -0
  18. scout/demo/resources/ensembl_transcripts_37_reduced.txt +27 -0
  19. scout/demo/resources/ensembl_transcripts_38_reduced.txt +36 -0
  20. scout/demo/resources/hgnc_reduced_set.txt +2 -0
  21. scout/log/handlers.py +2 -1
  22. scout/log/log.py +48 -61
  23. scout/models/case/case_loading_models.py +2 -0
  24. scout/parse/omim.py +2 -2
  25. scout/server/app.py +23 -7
  26. scout/server/blueprints/alignviewers/controllers.py +46 -23
  27. scout/server/blueprints/cases/controllers.py +21 -47
  28. scout/server/blueprints/cases/templates/cases/case_report.html +4 -1
  29. scout/server/blueprints/cases/templates/cases/case_sma.html +19 -0
  30. scout/server/blueprints/cases/templates/cases/collapsible_actionbar.html +7 -7
  31. scout/server/blueprints/cases/templates/cases/individuals_table.html +1 -1
  32. scout/server/blueprints/clinvar/form.py +1 -1
  33. scout/server/blueprints/clinvar/templates/clinvar/clinvar_submissions.html +2 -2
  34. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +9 -3
  35. scout/server/blueprints/institutes/controllers.py +11 -38
  36. scout/server/blueprints/institutes/forms.py +18 -4
  37. scout/server/blueprints/institutes/templates/overview/cases.html +137 -46
  38. scout/server/blueprints/login/views.py +16 -12
  39. scout/server/blueprints/panels/controllers.py +4 -1
  40. scout/server/blueprints/panels/templates/panels/panel.html +1 -1
  41. scout/server/blueprints/panels/templates/panels/panels.html +5 -5
  42. scout/server/blueprints/public/templates/public/index.html +18 -10
  43. scout/server/blueprints/variant/templates/variant/components.html +0 -1
  44. scout/server/blueprints/variant/templates/variant/gene_disease_relations.html +1 -1
  45. scout/server/config.py +3 -0
  46. scout/server/extensions/__init__.py +2 -0
  47. scout/server/extensions/chanjo2_extension.py +46 -0
  48. scout/server/extensions/chanjo_extension.py +44 -1
  49. scout/server/extensions/matchmaker_extension.py +0 -1
  50. scout/server/links.py +11 -2
  51. scout/server/templates/report_base.html +1 -0
  52. scout/utils/convert.py +1 -1
  53. scout/utils/date.py +1 -1
  54. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/METADATA +1 -1
  55. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/RECORD +59 -58
  56. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/LICENSE +0 -0
  57. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/WHEEL +0 -0
  58. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/entry_points.txt +0 -0
  59. {scout_browser-4.88.1.dist-info → scout_browser-4.89.dist-info}/top_level.txt +0 -0
@@ -47,16 +47,20 @@ def load_user(user_id):
47
47
  @public_endpoint
48
48
  def login():
49
49
  """Login a user if they have access."""
50
- if "next" in request.args:
51
- session["next_url"] = request.args["next"]
50
+
51
+ if current_app.config.get("USERS_ACTIVITY_LOG_PATH"):
52
+ if request.form.get("consent_checkbox") is None and "consent_given" not in session:
53
+ flash(
54
+ "Logging user activity is a requirement for using this site and accessing your account. Without consent to activity logging, you will not be able to log in into Scout.",
55
+ "warning",
56
+ )
57
+ return redirect(url_for("public.index"))
58
+ session["consent_given"] = True
52
59
 
53
60
  user_id = None
54
61
  user_mail = None
55
62
 
56
- if (
57
- current_app.config.get("LDAP_HOST", current_app.config.get("LDAP_SERVER"))
58
- and request.method == "POST"
59
- ):
63
+ if current_app.config.get("LDAP_HOST", current_app.config.get("LDAP_SERVER")):
60
64
  ldap_authorized = controllers.ldap_authorized(
61
65
  request.form.get("ldap_user"), request.form.get("ldap_password")
62
66
  )
@@ -66,20 +70,19 @@ def login():
66
70
  flash("User not authorized by LDAP server", "warning")
67
71
  return redirect(url_for("public.index"))
68
72
 
69
- if current_app.config.get("GOOGLE"):
73
+ elif current_app.config.get("GOOGLE"):
70
74
  if session.get("email"):
71
75
  user_mail = session["email"]
72
76
  session.pop("email", None)
73
77
  else:
74
- LOG.info("Google Login!")
75
78
  redirect_uri = url_for(".authorized", _external=True)
76
79
  try:
77
80
  return oauth_client.google.authorize_redirect(redirect_uri)
78
81
  except Exception as ex:
79
82
  flash("An error has occurred while logging in user using Google OAuth")
80
83
 
81
- if request.args.get("email"): # log in against Scout database
82
- user_mail = request.args.get("email")
84
+ elif request.form.get("email"): # log in against Scout database
85
+ user_mail = request.form["email"]
83
86
  LOG.info("Validating user %s email %s against Scout database", user_id, user_mail)
84
87
 
85
88
  user_obj = store.user(email=user_mail, user_id=user_id)
@@ -101,6 +104,7 @@ def login():
101
104
  @public_endpoint
102
105
  def authorized():
103
106
  """Google auth callback function"""
107
+
104
108
  token = oauth_client.google.authorize_access_token()
105
109
  google_user = oauth_client.google.parse_id_token(token, None)
106
110
  session["email"] = google_user.get("email").lower()
@@ -116,6 +120,7 @@ def logout():
116
120
  session.pop("email", None)
117
121
  session.pop("name", None)
118
122
  session.pop("locale", None)
123
+ session.pop("consent_given", None)
119
124
  flash("you logged out", "success")
120
125
  return redirect(url_for("public.index"))
121
126
 
@@ -130,7 +135,6 @@ def users():
130
135
  def perform_login(user_dict):
131
136
  if login_user(user_dict, remember=True):
132
137
  flash("you logged in as: {}".format(user_dict.name), "success")
133
- next_url = session.pop("next_url", None)
134
- return redirect(request.args.get("next") or next_url or url_for("cases.index"))
138
+ return redirect(url_for("cases.index"))
135
139
  flash("sorry, you could not log in", "warning")
136
140
  return redirect(url_for("public.index"))
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import datetime as dt
3
3
  import logging
4
+ import re
4
5
 
5
6
  from flask import flash, redirect
6
7
  from flask_login import current_user
@@ -309,9 +310,11 @@ def downloaded_panel_name(panel_obj, format) -> str:
309
310
  Returns:
310
311
  a string describing the panel
311
312
  """
313
+ sanitized_panel_id = re.sub(r"[^a-zA-Z_\-]+", "", panel_obj["panel_name"])
314
+
312
315
  return "_".join(
313
316
  [
314
- panel_obj["panel_name"],
317
+ sanitized_panel_id,
315
318
  str(panel_obj["version"]),
316
319
  dt.datetime.now().strftime(DATE_DAY_FORMATTER),
317
320
  f"scout.{format}",
@@ -76,7 +76,7 @@
76
76
  </li>
77
77
  <li class="list-group-item">
78
78
  <label for="date">Date</label>
79
- <span class="float-end" id="date">{{ panel.date.date() }}</span>
79
+ <span class="float-end" id="date">{{panel._id.generation_time.strftime('%Y-%m-%d')}} {% if panel.hidden %} deleted:{{ panel.date.date()}} {% endif %}</span>
80
80
  </li>
81
81
  <li class="list-group-item">
82
82
  <label for="mainainer">Maintainers</label>
@@ -109,7 +109,7 @@
109
109
  </div>
110
110
  <div class="col-sm-3 text-center">
111
111
  <input type="text" name="new_panel_name" class="form-control" placeholder="Panel ID" required
112
- pattern="^[a-zA-Z0-9_-]*$" title="Only alphanumeric characters (A-Z+a-z+0-9), hyphens, and underscores allowed.">
112
+ pattern="[A-Za-z0-9_\-]+" title="Only alphanumeric characters (A-Z+a-z+0-9), hyphens, and underscores allowed.">
113
113
  </div>
114
114
  <div class="col-sm-4 text-center">
115
115
  <input type="text" name="display_name" class="form-control" placeholder="Full name">
@@ -150,8 +150,8 @@
150
150
  <table class="table table-striped" id="panelTable" style="table-layout: fixed;">
151
151
  <thead>
152
152
  <tr>
153
- <th style="width: 30%">Name</th>
154
- <th>Version</th>
153
+ <th style="width: 20%">Name</th>
154
+ <th style="width: 20%">Version</th>
155
155
  <th>Number of genes</th>
156
156
  <th>History</th>
157
157
  <th></th>
@@ -170,10 +170,10 @@
170
170
  <span class="badge bg-danger" data-bs-toggle="tooltip" title="This panel was removed">Removed</span>
171
171
  {% endif %}
172
172
  </td>
173
- <td>{{ panel.version }} ({{ panel.date.date()}})</td>
173
+ <td>{{ panel.version }} ({{panel._id.generation_time.strftime('%Y-%m-%d')}} {% if panel.hidden %} deleted:{{ panel.date.date()}} {% endif %})</td>
174
174
  <td><span class="badge rounded-pill bg-secondary">{{ panel.genes|length}}</span></td>
175
175
  <td><button id="{{panel._id}}" type="button" data-bs-toggle="collapse" href="#paneldiv_{{panel._id}}" aria-expanded="false" aria-controls="paneldiv_{{panel._id}}" class="btn btn-primary btn-xs"><span class="fa fa-search-plus"></span></button><br>{{ history_view(panel._id, panel.panel_name) }}</td>
176
- <td><button id="{{panel._id}}" type="button" data-bs-toggle="collapse" href="#modifydiv_{{panel._id}}" aria-expanded="false" aria-controls="modifydiv_{{panel._id}}" class="btn btn-warning btn-xs"><span class="fa fa-pen-square"></span></button><br>{{ history_view(panel._id, panel.panel_name) }}</td>
176
+ <td><button id="{{panel._id}}" type="button" data-bs-toggle="collapse" href="#modifydiv_{{panel._id}}" aria-expanded="false" aria-controls="modifydiv_{{panel._id}}" class="btn btn-warning btn-xs"><span class="fa fa-pen-square"></span></button></td>
177
177
  <td>
178
178
  {% if panel.hidden %}
179
179
  <form action="{{ url_for('panels.panel_restore', panel_id=panel._id) }}" method="POST">
@@ -20,12 +20,12 @@
20
20
  </a>
21
21
  </p>
22
22
  {% else %}
23
- {% if config.GOOGLE %}
24
- <a class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" role="button">
25
- Login with Google
26
- </a>
27
- {% elif config.LDAP_HOST or config.LDAP_SERVER %}
28
- <form class="row" method="POST" action="{{ url_for('login.login') }}">
23
+ <form class="row" method="POST" action="{{ url_for('login.login') }}">
24
+ {% if config.GOOGLE %}
25
+ <button name="googleLogin" class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" type="submit">
26
+ Login with Google
27
+ </button>
28
+ {% elif config.LDAP_HOST or config.LDAP_SERVER %}
29
29
  <div class="col-5">
30
30
  <input type="text" name="ldap_user" class="form-control" placeholder="user email/id" required>
31
31
  </div>
@@ -35,9 +35,7 @@
35
35
  <div class="col-2">
36
36
  <button type="submit" class="btn btn-primary form-control text-white">Login</button>
37
37
  </div>
38
- </form>
39
38
  {% else %}
40
- <form action="{{ url_for('login.login') }}">
41
39
  <div class="row">
42
40
  <div class="col-8">
43
41
  <input type="text" placeholder="email" class="form-control" name="email">
@@ -46,8 +44,18 @@
46
44
  <button type="submit" class="btn btn-primary form-control text-white">Login</button>
47
45
  </div>
48
46
  </div>
49
- </form>
50
- {% endif %}
47
+ {% endif %}
48
+ <!-- Consent Banner -->
49
+ {% if config.USERS_ACTIVITY_LOG_PATH %}
50
+ <div class="row mt-3 ms-0 alert-warning" style="text-align: center; padding: 20px;">
51
+ <p>To ensure security and improve functionality, we log user activity, including interactions and navigation. This helps us enhance performance and maintain platform safety.
52
+ By checking the box, you agree that your activity will be logged, which is required to use this portal and log in.</p>
53
+ <label>
54
+ <input type="checkbox" name="consent_checkbox"> I agree to my activity being logged
55
+ </label>
56
+ </div>
57
+ {% endif %}
58
+ </form>
51
59
  {% endif %}
52
60
  </div>
53
61
  <div class="container bg-light rounded-3 py-4">
@@ -273,7 +273,6 @@
273
273
  {% if gene.decipher_link %}
274
274
  <a href="{{ gene.decipher_link }}" class="btn btn-secondary text-white" rel="noopener" referrerpolicy="no-referrer" target="_blank">Decipher</a>
275
275
  {% endif %}
276
- <a href="http://tools.genes.toronto.edu/" class="btn btn-secondary text-white" rel="noopener" referrerpolicy="no-referrer" target="_blank">SPANR</a>
277
276
  <a href="{{ gene.reactome_link }}" class="btn btn-secondary text-white" rel="noopener" referrerpolicy="no-referrer" target="_blank">Reactome</a>
278
277
  <a href="{{ gene.expression_atlas_link }}" class="btn btn-secondary text-white" rel="noopener" referrerpolicy="no-referrer" target="_blank">Expr. Atlas</a>
279
278
  <a href="{{ gene.clingen_link }}" class="btn btn-secondary text-white" rel="noopener" referrerpolicy="no-referrer" target="_blank">ClinGen</a>
@@ -50,7 +50,7 @@
50
50
  {% if gene.common and gene.disease_terms %}
51
51
  {% for disease_term in gene.disease_terms %}
52
52
  {% if disease_term.source == 'ORPHA' %}
53
- <tr data-bs-toggle="tooltip" title="Some ORPHA disorders are phenotypic umbrella terms for multiple genetic entities. The inheritance models are in this case a set derived from all those entities, not necessarily the inheritance mode known for this gene.">
53
+ <tr data-bs-toggle="tooltip" title="Some ORPHA disorders are phenotypic umbrella terms for multiple genetic entities. The inheritance models are in this case a set derived from all those entities, not necessarily modes of inheritance known for this gene. ORPHA inheritance modes will not be shown on the general case report.">
54
54
  <td>
55
55
  <a href="http://omim.org/entry/{{ gene.common.omim_id }}" rel="noopener" target="_blank">
56
56
  {{ gene.common.hgnc_symbol }}
scout/server/config.py CHANGED
@@ -151,3 +151,6 @@ SV_RANK_MODEL_LINK_POSTFIX = "-.ini"
151
151
  # BIONANO_ACCESS = "https://bionano-access.scilifelab.se"
152
152
  # BIONANO_USERNAME = "USERNAME"
153
153
  # BIONANO_PASSWORD = "PASSWORD"
154
+
155
+ # Uncomment and customise to log users' activity to file
156
+ # USERS_ACTIVITY_LOG_PATH = "users_activity.log"
@@ -10,6 +10,7 @@ from scout.utils.track_resources import AlignTrackHandler
10
10
 
11
11
  from .beacon_extension import Beacon
12
12
  from .bionano_extension import BioNanoAccessAPI
13
+ from .chanjo2_extension import Chanjo2Client
13
14
  from .chanjo_extension import ChanjoReport
14
15
  from .clinvar_extension import ClinVarApi
15
16
  from .gens_extension import GensViewer
@@ -37,3 +38,4 @@ beacon = Beacon()
37
38
  config_igv_tracks = AlignTrackHandler()
38
39
  bionano_access = BioNanoAccessAPI()
39
40
  chanjo_report = ChanjoReport()
41
+ chanjo2 = Chanjo2Client()
@@ -0,0 +1,46 @@
1
+ import logging
2
+ from typing import Dict
3
+
4
+ import requests
5
+ from flask import current_app
6
+
7
+ REF_CHROM = "14"
8
+ MT_CHROM = "MT"
9
+ LOG = logging.getLogger(__name__)
10
+
11
+
12
+ class Chanjo2Client:
13
+ """Runs requests to chanjo2 and returns results in the expected format."""
14
+
15
+ def mt_coverage_stats(self, individuals: dict) -> Dict[str, dict]:
16
+ """Sends a POST requests to the chanjo2 coverage/d4/interval to collect stats for the MT case report."""
17
+
18
+ chanjo2_chrom_cov_url: str = "/".join(
19
+ [current_app.config.get("CHANJO2_URL"), "coverage/d4/interval/"]
20
+ )
21
+ coverage_stats = {}
22
+ for ind in individuals:
23
+
24
+ if not ind.get("d4_file"):
25
+ continue
26
+ chrom_cov_query = {"coverage_file_path": ind["d4_file"]}
27
+
28
+ # Get mean coverage over chr14
29
+ chrom_cov_query["chromosome"] = REF_CHROM
30
+ resp = requests.post(chanjo2_chrom_cov_url, json=chrom_cov_query)
31
+ autosome_cov = resp.json().get("mean_coverage")
32
+
33
+ # Get mean coverage over chrMT
34
+ chrom_cov_query["chromosome"] = MT_CHROM
35
+ resp = requests.post(chanjo2_chrom_cov_url, json=chrom_cov_query)
36
+
37
+ mt_cov = resp.json().get("mean_coverage")
38
+
39
+ coverage_info = dict(
40
+ mt_coverage=mt_cov,
41
+ autosome_cov=autosome_cov,
42
+ mt_copy_number=round((mt_cov / autosome_cov) * 2, 2),
43
+ )
44
+ coverage_stats[ind["individual_id"]] = coverage_info
45
+
46
+ return coverage_stats
@@ -2,13 +2,16 @@
2
2
  Generate coverage reports using chanjo and chanjo-report. Documentation under -> `docs/admin-guide/chanjo_coverage_integration.md`
3
3
  """
4
4
 
5
+ import json
5
6
  import logging
6
7
 
7
- from flask import current_app, request
8
+ import requests
9
+ from flask import current_app, request, url_for
8
10
  from flask_babel import Babel
9
11
  from markupsafe import Markup
10
12
 
11
13
  LOG = logging.getLogger(__name__)
14
+ REF_CHROM_MT_STATS = "14"
12
15
 
13
16
 
14
17
  class ChanjoReport:
@@ -57,3 +60,43 @@ class ChanjoReport:
57
60
  app.register_blueprint(report_bp, url_prefix="/reports")
58
61
  app.config["chanjo_report"] = True
59
62
  app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True if app.debug else False
63
+
64
+ def mt_coverage_stats(self, individuals: dict) -> dict:
65
+ """Send a request to chanjo endpoint to retrieve MT vs autosome coverage stats
66
+
67
+ Args:
68
+ individuals(dict): case_obj["individuals"] object
69
+
70
+ Returns:
71
+ coverage_stats(dict): a dictionary with mean MT and autosome transcript coverage stats
72
+ """
73
+ coverage_stats = {}
74
+ ind_ids = []
75
+ for ind in individuals:
76
+ ind_ids.append(ind["individual_id"])
77
+
78
+ # Prepare complete url to Chanjo report chromosome mean coverage calculation endpoint
79
+ cov_calc_url = url_for("report.json_chrom_coverage", _external=True)
80
+ # Prepare request data to calculate mean MT coverage
81
+ data = dict(sample_ids=",".join(ind_ids), chrom="MT")
82
+ # Send POST request with data to chanjo endpoint
83
+ resp = requests.post(cov_calc_url, json=data)
84
+ mt_cov_data = json.loads(resp.text)
85
+
86
+ # Change request data to calculate mean autosomal coverage
87
+ data["chrom"] = REF_CHROM_MT_STATS
88
+ # Send POST request with data to chanjo endpoint
89
+ resp = requests.post(cov_calc_url, json=data)
90
+ ref_cov_data = json.loads(resp.text) # mean coverage over the transcripts of ref chrom
91
+
92
+ for ind in ind_ids:
93
+ if not (mt_cov_data.get(ind) and ref_cov_data.get(ind)):
94
+ continue
95
+ coverage_info = dict(
96
+ mt_coverage=round(mt_cov_data[ind], 2),
97
+ autosome_cov=round(ref_cov_data[ind], 2),
98
+ mt_copy_number=round((mt_cov_data[ind] / ref_cov_data[ind]) * 2, 2),
99
+ )
100
+ coverage_stats[ind] = coverage_info
101
+
102
+ return coverage_stats
@@ -3,7 +3,6 @@
3
3
  """
4
4
 
5
5
  import datetime
6
- import json
7
6
  import logging
8
7
 
9
8
  from werkzeug.datastructures import Headers
scout/server/links.py CHANGED
@@ -819,6 +819,15 @@ def alamut_gene_link(
819
819
  url_template(str): link to Alamut browser
820
820
  """
821
821
 
822
+ def get_gene_canonical_tx(gene: dict) -> str:
823
+ """Returns the canonical transcript of a gene."""
824
+ if gene.get("canonical_transcript"):
825
+ return gene["canonical_transcript"]
826
+
827
+ for tx in gene.get("transcripts"):
828
+ if tx.get("is_canonical"):
829
+ return tx["transcript_id"]
830
+
822
831
  if current_app.config.get("HIDE_ALAMUT_LINK"):
823
832
  return False
824
833
 
@@ -828,7 +837,7 @@ def alamut_gene_link(
828
837
  (search_verb, alamut_key_arg, alamut_inst_arg) = _get_alamut_config(institute_obj)
829
838
 
830
839
  url_template = (
831
- "http://localhost:10000/{search_verb}?{alamut_key_arg}{alamut_inst_arg}request={this[canonical_transcript]}{build_str}:"
840
+ "http://localhost:10000/{search_verb}?{alamut_key_arg}{alamut_inst_arg}request={canonical_transcript}{build_str}:"
832
841
  "{hgvs_identifier}"
833
842
  )
834
843
 
@@ -845,7 +854,7 @@ def alamut_gene_link(
845
854
  search_verb=search_verb,
846
855
  alamut_key_arg=alamut_key_arg,
847
856
  alamut_inst_arg=alamut_inst_arg,
848
- this=gene_obj,
857
+ canonical_transcript=get_gene_canonical_tx(gene=gene_obj),
849
858
  build_str=build_str,
850
859
  hgvs_identifier=quote(hgvs_raw),
851
860
  )
@@ -6,6 +6,7 @@
6
6
  {% block css %}
7
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='bs_styles.css') }}">
9
10
  <style>
10
11
  tr.light-grey td{
11
12
  background-color: LightGray;
scout/utils/convert.py CHANGED
@@ -53,7 +53,7 @@ def amino_acid_residue_change_3_to_1(protein_sequence_name):
53
53
  if protein_sequence_name is None:
54
54
  return None
55
55
 
56
- p = re.compile("p.([A-Za-z]+)(\d+)([A-Za-z]+)")
56
+ p = re.compile(r"p\.([A-Za-z]+)(\d+)([A-Za-z]+)")
57
57
  m = p.match(protein_sequence_name)
58
58
  if m is None:
59
59
  return None
scout/utils/date.py CHANGED
@@ -11,7 +11,7 @@ def match_date(date):
11
11
  Returns:
12
12
  bool
13
13
  """
14
- date_pattern = re.compile("^(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])")
14
+ date_pattern = re.compile(r"^(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])")
15
15
  if re.match(date_pattern, date):
16
16
  return True
17
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scout-browser
3
- Version: 4.88.1
3
+ Version: 4.89
4
4
  Summary: Clinical DNA variant visualizer and browser.
5
5
  Home-page: https://github.com/Clinical-Genomics/scout
6
6
  Author: Måns Magnusson