clinicedc 2.0.15__py3-none-any.whl → 2.0.17__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.

Potentially problematic release.


This version of clinicedc might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clinicedc
3
- Version: 2.0.15
3
+ Version: 2.0.17
4
4
  Summary: A clinical trials data management framework built on Django
5
5
  Keywords: django,clinicedc,edc,clinical trials,research,data management,esource
6
6
  Author: Erik van Widenfelt, Jonathan Willitts
@@ -1087,13 +1087,13 @@ edc_fieldsets/models.py,sha256=sAQOBRo_PM7Uu3Hvxx2j5CLDDpuei8-vAYdkOUSuYkg,32
1087
1087
  edc_fieldsets/urls.py,sha256=_SQMjzRQvSHqalE_zviNqzpbcwJgLdjAgOh3-Eprb58,749
1088
1088
  edc_form_describer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1089
1089
  edc_form_describer/apps.py,sha256=unr6UihcCOaoVtj56Q6DRQq9WDjivoE1zZjyy2HTBqU,121
1090
- edc_form_describer/form_describer.py,sha256=w94XyAabCOGhuSE6sGedvoPEkTBxpNp6skxR_tsNkvQ,8204
1091
- edc_form_describer/forms_reference.py,sha256=U9P0rRq5fDmUoplXnou6VqyusHVpC9u1wu-kW2jyFgc,4684
1092
- edc_form_describer/make_forms_reference.py,sha256=JdeR1lMo4A2NTvgschdSR-TPpHXvmCO_hlPcK7zKo4U,1371
1090
+ edc_form_describer/form_describer.py,sha256=0F7eu2rLpThwv9lU7taSZFr6mJed1Gpv6hUeMYEWlGA,8435
1091
+ edc_form_describer/forms_reference.py,sha256=TYofCReEipIceNXSddL29jcApYUrgU8LNq5UElJtSz0,4709
1092
+ edc_form_describer/make_forms_reference.py,sha256=wQY39EFscPWTszj8gzpLhf0WKktL91uIVBdx1cMh_gU,1327
1093
1093
  edc_form_describer/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1094
1094
  edc_form_describer/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1095
- edc_form_describer/management/commands/make_forms_reference.py,sha256=KOUOCN27YvAsBjSQ-0gDk98wnltNoVnrt8k4JJyBWBk,3123
1096
- edc_form_describer/markdown_writer.py,sha256=KILzp0HAY2omaw3SnLTjW9KcjpvJkzjAvyaZ_on7bkI,1799
1095
+ edc_form_describer/management/commands/make_forms_reference.py,sha256=Pni3ZPpGM5Ds7-GYXPvl_m2zxHOE2_rFvmdlXre0FnM,3168
1096
+ edc_form_describer/markdown_writer.py,sha256=UFjVvz5cUHyJnlUDNm6RIPBGc675HL-IsoUS6TDrKJg,1809
1097
1097
  edc_form_describer/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1098
1098
  edc_form_describer/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1099
1099
  edc_form_describer/urls.py,sha256=_FX1x4JJH8wKclMUbJI847xPbjxqHJazbRZYj30wW7g,113
@@ -1966,8 +1966,8 @@ edc_model_form/utils.py,sha256=gGe3etrp5YF4AKMGFIR3I9V1CfX4c5Wm6fK-8JkUeT4,538
1966
1966
  edc_model_to_dataframe/__init__.py,sha256=iDR_F5oKkKDL9FfBXl0WKpwc2iOWbOlY-8wpCipBNnQ,115
1967
1967
  edc_model_to_dataframe/apps.py,sha256=US1ehhJPgkvmIDZXiUPMoSjHTWIztUbYGtx_azTXPIQ,290
1968
1968
  edc_model_to_dataframe/constants.py,sha256=xgCr2ZXUsjZqf4wfpY0JBhQoARzQEVS8tnkfBWwOMVQ,702
1969
- edc_model_to_dataframe/model_to_dataframe.py,sha256=DDuDIEpgEHjTTrqcjdYMLERvFl9h-HHhxYaapFepO9U,18829
1970
- edc_model_to_dataframe/read_frame_edc.py,sha256=D3Bai4ce4u6942OU8sad1OEAeDRIT1DQxYOQib5AMDE,791
1969
+ edc_model_to_dataframe/model_to_dataframe.py,sha256=Ut-Ixi7eoppBlnlgq4gBnhBh3ts13n1UjLMGQcKY7Bs,18684
1970
+ edc_model_to_dataframe/read_frame_edc.py,sha256=aOj3FUUvTCr-V-RmkBQtSGpg46zpWfX40HfOT3eRS4U,800
1971
1971
  edc_model_to_dataframe/urls.py,sha256=KChLHeE6yfONC6IdMm3Q4U3u7tmy_Mb1w2lrbpFz96g,148
1972
1972
  edc_navbar/__init__.py,sha256=Y95juevvOawGZmOV3Ofef7oGwYSWWttAYT0v0mehkJc,195
1973
1973
  edc_navbar/apps.py,sha256=fiWHsCYaDnHXNfYUu6DuvlAcO2k45-zkdbuXwXI3ksc,367
@@ -3287,7 +3287,7 @@ edc_visit_schedule/navbars.py,sha256=RWx57OD-KxpPh0ihRyangGGiOOR7vfxRFjPCj8C5uF4
3287
3287
  edc_visit_schedule/ordered_collection.py,sha256=7A5YqG0qGx6jAE0z6bd3BijlW9oYxiQrF-5fzB3hvok,1699
3288
3288
  edc_visit_schedule/post_migrate_signals.py,sha256=7vFfx7ifMwmvrf7MCzpk9q4g1pbM4n44uM4TrH1hPGQ,748
3289
3289
  edc_visit_schedule/schedule/__init__.py,sha256=bzyYW9y3pPCFTjwjvkUVgkW3d1g1D2SCNfgE9bnLjII,101
3290
- edc_visit_schedule/schedule/schedule.py,sha256=daKuzIx0sa9c4RjedXQtFFzx79bqdEvAJEDNmzZ8R-s,14874
3290
+ edc_visit_schedule/schedule/schedule.py,sha256=RhUc5UPKZacqYmdxI8-tkEkERZFfq0TAN-wDF8dBMEU,14881
3291
3291
  edc_visit_schedule/schedule/visit_collection.py,sha256=9OJKrLtx-H0eL9oo0dsRqYGPICiAMqEF4xIhhlinx0A,2033
3292
3292
  edc_visit_schedule/schedule/window.py,sha256=P_hNhAXBmRxED_0UrlpQoYHF5r_nTEpTK7U2JSAnpCs,5222
3293
3293
  edc_visit_schedule/simple_model_validator.py,sha256=pE2QCSI1VhSr9Dd-NGd-72u6e479_vzbS8iL17eg4Yc,771
@@ -3406,7 +3406,7 @@ edc_vitals/models/fields/waist_circumference.py,sha256=fZcHFDdEwWLjIVLktKrFCD9UU
3406
3406
  edc_vitals/models/fields/weight.py,sha256=zo9_9e3Cpu0UqoRbWS-iDkcDo6fK80b1dDQy4x4MyxE,921
3407
3407
  edc_vitals/utils.py,sha256=vXid44KUXxeaSyund_y5MNXc5DFJs052_PwUAjszE2k,1384
3408
3408
  edc_vitals/validators.py,sha256=vNiElWMs0rRnHRNuVoPLRf0rW_C_0xcfUyep1Y_Si5s,156
3409
- clinicedc-2.0.15.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
- clinicedc-2.0.15.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
- clinicedc-2.0.15.dist-info/METADATA,sha256=OYiw0Dnv4nYU6PEi6gKTj-xbM-7sc6meDbengt5P2zY,15899
3412
- clinicedc-2.0.15.dist-info/RECORD,,
3409
+ clinicedc-2.0.17.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
+ clinicedc-2.0.17.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
+ clinicedc-2.0.17.dist-info/METADATA,sha256=Vh9mgF060UWvEzXQ9O7I8XTXxKqDH9REwVJLEdtoDrs,15899
3412
+ clinicedc-2.0.17.dist-info/RECORD,,
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import re
4
5
  import string
5
6
  import sys
6
- from datetime import datetime
7
7
  from math import floor
8
8
 
9
9
  from django.core.management.color import color_style
10
+ from django.utils import timezone
10
11
 
11
12
  from edc_fieldsets.fieldsets import Fieldsets
12
13
  from edc_model.constants import DEFAULT_BASE_FIELDS
@@ -57,14 +58,20 @@ class FormDescriber:
57
58
  self.markdown: list[str] = []
58
59
  add_timestamp = True if add_timestamp is None else add_timestamp
59
60
  self.anchor_prefix = anchor_prefix or self.anchor_prefix
60
- timestamp = datetime.today().strftime("%Y-%m-%d %H:%M")
61
+ timestamp = timezone.now().strftime("%Y-%m-%d %H:%M")
61
62
  self.level = level or self.level
62
63
  self.conditional_fieldset = None
63
64
  self.admin_cls = admin_cls
64
65
  try:
65
66
  self.model_cls = admin_cls.model
66
67
  except AttributeError:
67
- self.model_cls = admin_cls.form._meta.model
68
+ try:
69
+ self.model_cls = admin_cls.form._meta.model
70
+ except AttributeError as e:
71
+ raise FormDescriberError(
72
+ f"Unable to determine admin class model. Got {admin_cls}"
73
+ ) from e
74
+
68
75
  self.visit_code = visit_code
69
76
  self.models_fields = {fld.name: fld for fld in self.model_cls._meta.get_fields()}
70
77
 
@@ -115,7 +122,7 @@ class FormDescriber:
115
122
  def anchor(self):
116
123
  allow = string.ascii_letters + string.digits + "-"
117
124
  slug = self.verbose_name.lower().replace(" ", "-")
118
- slug = re.sub("[^%s]" % allow, "", slug)
125
+ slug = re.sub(f"[^{allow}]", "", slug)
119
126
  return f"{self.anchor_prefix}-{slug}"
120
127
 
121
128
  def describe(self):
@@ -135,7 +142,7 @@ class FormDescriber:
135
142
 
136
143
  for fieldset_name, fieldset in self.fieldsets:
137
144
  if fieldset_name not in ["Audit"]:
138
- fieldset_name = fieldset_name or "Main"
145
+ fieldset_name = fieldset_name or "Main" # noqa: PLW2901
139
146
  self.markdown.append(f"\n**Section: {fieldset_name}**")
140
147
  if fieldset.get("classes") != "collapse":
141
148
  for fname in fieldset.get("fields"):
@@ -172,10 +179,8 @@ class FormDescriber:
172
179
  self.markdown.append(f"* custom_prompt: *{self.custom_form_labels.get(fname)}*")
173
180
  self.markdown.append(f"- db_table: {self.model_cls._meta.db_table}")
174
181
  self.markdown.append(f"- column: {field_cls.name}")
175
- try:
182
+ with contextlib.suppress(AttributeError):
176
183
  self.markdown.append(f"- metadata: {field_cls.metadata}")
177
- except AttributeError:
178
- pass
179
184
  self.markdown.append(f"- type: {field_cls.get_internal_type()}")
180
185
  if field_cls.max_length:
181
186
  self.markdown.append(f"- length: {field_cls.max_length}")
@@ -1,8 +1,9 @@
1
- from datetime import datetime
2
1
  from importlib.metadata import version
2
+ from pathlib import Path
3
3
 
4
4
  from django.apps import apps as django_apps
5
5
  from django.conf import settings
6
+ from django.utils import timezone
6
7
 
7
8
  from .form_describer import FormDescriber
8
9
  from .markdown_writer import MarkdownWriter
@@ -36,24 +37,20 @@ class FormsReference:
36
37
  self.add_per_form_timestamp = (
37
38
  True if add_per_form_timestamp is None else add_per_form_timestamp
38
39
  )
39
- self.timestamp = datetime.today().strftime("%Y-%m-%d %H:%M")
40
+ self.timestamp = timezone.now().strftime("%Y-%m-%d %H:%M")
40
41
  for visit_schedule in self.visit_schedules:
41
42
  self.plans.update({visit_schedule.name: {}})
42
43
  for schedule in visit_schedule.schedules.values():
43
44
  for visit_code, visit in schedule.visits.items():
44
- crfs = []
45
- requisitions = []
46
- for c in visit.crfs:
47
- crfs.append(c.model)
48
- for r in visit.requisitions:
49
- requisitions.append(r.panel.name)
45
+ crfs = [c.model for c in visit.crfs]
46
+ requisitions = [r.panel.name for r in visit.requisitions]
50
47
  self.plans[visit_schedule.name].update(
51
48
  {visit_code: {"crfs": crfs, "requisitions": requisitions}}
52
49
  )
53
50
 
54
51
  def to_file(
55
52
  self,
56
- path: str | None = None,
53
+ path: Path | str | None = None,
57
54
  overwrite: bool | None = None,
58
55
  pad: int | None = None,
59
56
  ):
@@ -95,23 +92,25 @@ class FormsReference:
95
92
  for index, model in enumerate(documents.get("crfs")):
96
93
  model_cls = django_apps.get_model(model)
97
94
  admin_cls = self.admin_site._registry.get(model_cls)
98
- describer = self.describer_cls(
99
- admin_cls=admin_cls,
100
- include_hidden_fields=self.include_hidden_fields,
101
- visit_code=visit_code,
102
- level=self.h4,
103
- anchor_prefix=self.anchor_prefix,
104
- add_timestamp=self.add_per_form_timestamp,
105
- )
106
- describer.markdown.append("\n")
107
- anchor = f"{self.get_anchor(describer.anchor)}"
108
- toc.append(
109
- f'{index + 1}. <a href="#{anchor}">{describer.verbose_name}</a>'
110
- )
111
- markdown.extend(describer.markdown)
95
+ if admin_cls:
96
+ describer = self.describer_cls(
97
+ admin_cls=admin_cls,
98
+ include_hidden_fields=self.include_hidden_fields,
99
+ visit_code=visit_code,
100
+ level=self.h4,
101
+ anchor_prefix=self.anchor_prefix,
102
+ add_timestamp=self.add_per_form_timestamp,
103
+ )
104
+ describer.markdown.append("\n")
105
+ anchor = f"{self.get_anchor(describer.anchor)}"
106
+ toc.append(
107
+ f'{index + 1}. <a href="#{anchor}">{describer.verbose_name}</a>'
108
+ )
109
+ markdown.extend(describer.markdown)
112
110
  markdown.append(f"{self.h4} Requisitions\n")
113
- for panel_name in documents.get("requisitions"):
114
- markdown.append(f"* {panel_name}\n")
111
+ markdown.extend(
112
+ [f"* {panel_name}\n" for panel_name in documents.get("requisitions")]
113
+ )
115
114
  markdown = self.insert_toc(toc, markdown)
116
115
  markdown.insert(0, f"{self.h1} {self.title}")
117
116
  markdown.append(
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
3
  import sys
5
4
  from importlib import import_module
6
5
 
@@ -16,11 +15,10 @@ style = color_style()
16
15
 
17
16
 
18
17
  def make_forms_reference(
19
- app_label: str = None,
20
- admin_site_name: str = None,
21
- visit_schedule_name: str = None,
22
- title: str = None,
23
- path: str | None = None,
18
+ app_label: str,
19
+ admin_site_name: str,
20
+ visit_schedule_name: str,
21
+ title: str | None = None,
24
22
  ):
25
23
  module = import_module(app_label)
26
24
  admin_site = getattr(module.admin_site, admin_site_name)
@@ -29,9 +27,9 @@ def make_forms_reference(
29
27
  sys.stdout.write(
30
28
  style.MIGRATE_HEADING(f"Refreshing CRF reference document for {app_label}\n")
31
29
  )
32
- doc_folder = os.path.join(settings.BASE_DIR, "docs")
33
- if not os.path.exists(doc_folder):
34
- os.mkdir(doc_folder)
30
+ doc_folder = settings.BASE_DIR / "docs"
31
+ if not doc_folder.exists():
32
+ doc_folder.mkdir()
35
33
 
36
34
  forms = FormsReference(
37
35
  visit_schedules=[visit_schedule],
@@ -40,8 +38,8 @@ def make_forms_reference(
40
38
  add_per_form_timestamp=False,
41
39
  )
42
40
 
43
- path = os.path.join(doc_folder, "forms_reference.md")
41
+ path = doc_folder / f"forms_reference_{app_label}.md"
44
42
  forms.to_file(path=path, overwrite=True)
45
43
 
46
- print(path)
47
- print("Done.")
44
+ sys.stdout.write(f"{path}\n")
45
+ sys.stdout.write("Done.\n")
@@ -17,16 +17,16 @@ style = color_style()
17
17
 
18
18
 
19
19
  def update_forms_reference(
20
- app_label: str = None,
21
- admin_site_name: str = None,
22
- visit_schedule_name: str = None,
23
- title: str = None,
20
+ app_label: str,
21
+ admin_site_name: str,
22
+ visit_schedule_name: str,
23
+ title: str | None = None,
24
24
  filename: str | None = None,
25
25
  doc_folder: str | None = None,
26
26
  ):
27
27
  module = import_module(app_label)
28
28
  default_doc_folder = Path(settings.BASE_DIR / "docs")
29
- filename = filename or "forms_reference.md"
29
+ filename = filename or f"forms_reference_{app_label}.md"
30
30
  admin_site = getattr(module.admin_site, admin_site_name)
31
31
  visit_schedule = site_visit_schedules.get_visit_schedule(visit_schedule_name)
32
32
  title = title or _("%(title_app)s Forms Reference") % dict(title_app=app_label.upper())
@@ -47,8 +47,8 @@ def update_forms_reference(
47
47
  path = doc_folder / filename
48
48
  forms.to_file(path=path, overwrite=True)
49
49
 
50
- print(path)
51
- print("Done.")
50
+ sys.stdout.write(f"{path}\n")
51
+ sys.stdout.write("Done\n")
52
52
 
53
53
 
54
54
  class Command(BaseCommand):
@@ -85,7 +85,7 @@ class Command(BaseCommand):
85
85
  default=None,
86
86
  )
87
87
 
88
- def handle(self, *args, **options):
88
+ def handle(self, *args, **options): # noqa: ARG002
89
89
  app_label = options["app_label"]
90
90
  admin_site_name = options["admin_site_name"]
91
91
  visit_schedule_name = options["visit_schedule_name"]
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
- from datetime import datetime
3
+ from pathlib import Path
4
+
5
+ from django.utils import timezone
5
6
 
6
7
 
7
8
  class MarkdownWriter:
@@ -11,23 +12,23 @@ class MarkdownWriter:
11
12
  @staticmethod
12
13
  def get_path(path: str | None = None, overwrite: bool | None = None) -> str:
13
14
  if not path:
14
- timestamp = datetime.today().strftime("%Y%m%d%H%M")
15
+ timestamp = timezone.now().strftime("%Y%m%d%H%M")
15
16
  path = f"forms_{timestamp}.md"
16
- if os.path.exists(path):
17
+ if Path(path).exists():
17
18
  if overwrite:
18
- os.remove(path)
19
+ Path(path).unlink()
19
20
  else:
20
21
  raise FileExistsError(f"File exists. Got '{path}'")
21
22
  return path
22
23
 
23
24
  @staticmethod
24
- def to_markdown(markdown: list[str] = None) -> str:
25
+ def to_markdown(markdown: list[str]) -> str:
25
26
  """Returns the markdown as a text string."""
26
27
  return "\n".join(markdown)
27
28
 
28
29
  def to_file(
29
30
  self,
30
- markdown: list[str] = None,
31
+ markdown: list[str],
31
32
  pad: int | None = None,
32
33
  append: bool | None = None,
33
34
  prepend: bool | None = None,
@@ -42,9 +43,9 @@ class MarkdownWriter:
42
43
  else:
43
44
  self._write(markdown)
44
45
 
45
- def _write(self, markdown: str = None, mode: str | None = None) -> None:
46
+ def _write(self, markdown: str, mode: str | None = None) -> None:
46
47
  mode = mode or "w"
47
- with open(self.path, mode) as f:
48
+ with Path(self.path).open(mode) as f:
48
49
  f.write(markdown)
49
50
 
50
51
  def _append(self, markdown) -> None:
@@ -53,7 +54,7 @@ class MarkdownWriter:
53
54
 
54
55
  def _prepend(self, markdown=None) -> None:
55
56
  mode = "r+"
56
- with open(self.path, mode) as f:
57
+ with Path(self.path).open(mode) as f:
57
58
  content = f.read()
58
59
  f.seek(0, 0)
59
60
  f.write(markdown + "\n" + content)
@@ -1,27 +1,30 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from copy import copy
4
- from typing import TYPE_CHECKING
5
5
 
6
6
  import numpy as np
7
7
  import pandas as pd
8
8
  from django.apps import apps as django_apps
9
+ from django.contrib.sites.models import Site
9
10
  from django.core.exceptions import FieldError
10
11
  from django.db import OperationalError
12
+ from django.db.models import QuerySet
11
13
  from django_crypto_fields.utils import get_encrypted_fields, has_encrypted_fields
12
14
  from django_pandas.io import read_frame
15
+ from pandas import Series
13
16
 
14
- from .constants import ACTION_ITEM_COLUMNS, SYSTEM_COLUMNS
17
+ from edc_lab.models import Panel
18
+ from edc_list_data.model_mixins import ListModelMixin, ListUuidModelMixin
19
+ from edc_model.models import BaseUuidModel
20
+ from edc_sites.model_mixins import SiteModelMixin
15
21
 
16
- if TYPE_CHECKING:
17
- from django.db.models import QuerySet
22
+ from .constants import ACTION_ITEM_COLUMNS, SYSTEM_COLUMNS
18
23
 
19
- from edc_model.models import BaseUuidModel
20
- from edc_sites.model_mixins import SiteModelMixin
21
24
 
22
- class MyModel(SiteModelMixin, BaseUuidModel):
23
- class Meta(BaseUuidModel.Meta):
24
- pass
25
+ class MyModel(SiteModelMixin, BaseUuidModel):
26
+ class Meta(BaseUuidModel.Meta):
27
+ pass
25
28
 
26
29
 
27
30
  __all__ = ["ModelToDataframe", "ModelToDataframeError"]
@@ -52,16 +55,16 @@ class ModelToDataframe:
52
55
  See also: get_crf()
53
56
  """
54
57
 
55
- sys_field_names: list[str] = [
58
+ sys_field_names: tuple[str, ...] = (
56
59
  "_state",
57
60
  "_user_container_instance",
58
61
  "_domain_cache",
59
62
  "using",
60
63
  "slug",
61
- ]
62
- edc_sys_columns: list[str] = SYSTEM_COLUMNS
63
- action_item_columns: list[str] = ACTION_ITEM_COLUMNS
64
- illegal_chars: dict[str] = {
64
+ )
65
+ edc_sys_columns: tuple[str, ...] = SYSTEM_COLUMNS
66
+ action_item_columns: tuple[str, ...] = ACTION_ITEM_COLUMNS
67
+ illegal_chars: dict[str, str] = { # noqa: RUF012
65
68
  "\u2019": "'",
66
69
  "\u2018": "'",
67
70
  "\u201d": '"',
@@ -72,7 +75,7 @@ class ModelToDataframe:
72
75
  def __init__(
73
76
  self,
74
77
  model: str | None = None,
75
- queryset: QuerySet | None = None,
78
+ queryset: [QuerySet] | None = None,
76
79
  query_filter: dict | None = None,
77
80
  decrypt: bool | None = None,
78
81
  drop_sys_columns: bool | None = None,
@@ -104,7 +107,7 @@ class ModelToDataframe:
104
107
  try:
105
108
  self.model_cls = django_apps.get_model(self.model)
106
109
  except LookupError as e:
107
- raise LookupError(f"Model is {self.model}. Got `{e}`")
110
+ raise LookupError(f"Model is {self.model}. Got `{e}`") from e
108
111
  if self.sites:
109
112
  try:
110
113
  if queryset:
@@ -151,7 +154,7 @@ class ModelToDataframe:
151
154
 
152
155
  dataframe = self.merge_m2ms(dataframe)
153
156
 
154
- dataframe.rename(columns=self.columns, inplace=True)
157
+ dataframe = dataframe.rename(columns=self.columns)
155
158
 
156
159
  # remove timezone if asked
157
160
  if self.remove_timezone:
@@ -174,12 +177,12 @@ class ModelToDataframe:
174
177
  dataframe[column] = dataframe[column].dt.total_seconds()
175
178
 
176
179
  # fillna
177
- dataframe.fillna(value=np.nan, axis=0, inplace=True)
180
+ dataframe = dataframe.fillna(value=np.nan, axis=0)
178
181
 
179
182
  # remove illegal chars
180
183
  for column in list(dataframe.select_dtypes(include=["object"]).columns):
181
184
  dataframe[column] = dataframe.apply(
182
- lambda x: self._clean_chars(x[column]), axis=1
185
+ lambda x, col=column: self._clean_chars(x[col]), axis=1
183
186
  )
184
187
  self._dataframe = dataframe
185
188
  return self._dataframe
@@ -188,7 +191,7 @@ class ModelToDataframe:
188
191
  queryset = self.queryset.values_list(*self.columns).filter(**self.query_filter)
189
192
  return pd.DataFrame(list(queryset), columns=[v for v in self.columns])
190
193
 
191
- def get_dataframe_with_encrypted_fields(self, row_count: int) -> pd.DataFrame:
194
+ def get_dataframe_with_encrypted_fields(self, row_count: int) -> pd.DataFrame: # noqa: ARG002
192
195
  df = read_frame(
193
196
  self.queryset.filter(**self.query_filter), verbose=self.read_frame_verbose
194
197
  )
@@ -233,28 +236,27 @@ class ModelToDataframe:
233
236
  dataframe = dataframe.merge(df_m2m, on="id", how="left")
234
237
  return dataframe
235
238
 
236
- def _clean_chars(self, s):
237
- try:
238
- s = s if s else s
239
- except ValueError:
240
- pass
241
- else:
242
- if s:
243
- for k, v in self.illegal_chars.items():
244
- try:
245
- s = s.replace(k, v)
246
- except (AttributeError, TypeError):
247
- break
248
- return s
239
+ def _clean_chars(self, s: Series) -> Series:
240
+ if not s.empty:
241
+ for k, v in self.illegal_chars.items():
242
+ try:
243
+ s = s.replace(k, v)
244
+ except (AttributeError, TypeError):
245
+ break
246
+ return s
247
+ return np.nan
249
248
 
250
249
  def move_sys_columns_to_end(self, columns: dict[str, str]) -> dict[str, str]:
251
250
  system_columns = [
252
251
  f.name for f in self.model_cls._meta.get_fields() if f.name in SYSTEM_COLUMNS
253
252
  ]
254
253
  new_columns = {k: v for k, v in columns.items() if k not in system_columns}
255
- if system_columns:
256
- if len(new_columns.keys()) != len(columns.keys()) and not self.drop_sys_columns:
257
- new_columns.update({k: k for k in system_columns})
254
+ if (
255
+ system_columns
256
+ and len(new_columns.keys()) != len(columns.keys())
257
+ and not self.drop_sys_columns
258
+ ):
259
+ new_columns.update({k: k for k in system_columns})
258
260
  return new_columns
259
261
 
260
262
  def move_action_item_columns(self, columns: dict[str, str]) -> dict[str, str]:
@@ -262,12 +264,11 @@ class ModelToDataframe:
262
264
  f.name for f in self.model_cls._meta.get_fields() if f.name in ACTION_ITEM_COLUMNS
263
265
  ]
264
266
  new_columns = {k: v for k, v in columns.items() if k not in ACTION_ITEM_COLUMNS}
265
- if action_item_columns:
266
- if (
267
- len(new_columns.keys()) != len(columns.keys())
268
- and not self.drop_action_item_columns
269
- ):
270
- new_columns.update({k: k for k in ACTION_ITEM_COLUMNS})
267
+ if action_item_columns and (
268
+ len(new_columns.keys()) != len(columns.keys())
269
+ and not self.drop_action_item_columns
270
+ ):
271
+ new_columns.update({k: k for k in ACTION_ITEM_COLUMNS})
271
272
  return new_columns
272
273
 
273
274
  @property
@@ -285,12 +286,10 @@ class ModelToDataframe:
285
286
  columns = {col: col for col in columns_list}
286
287
  for column_name in columns_list:
287
288
  if column_name.endswith("_visit_id"):
288
- try:
289
+ with contextlib.suppress(FieldError):
289
290
  columns = self.add_columns_for_subject_visit(
290
291
  column_name=column_name, columns=columns
291
292
  )
292
- except FieldError:
293
- pass
294
293
  if column_name.endswith("_requisition") or column_name.endswith(
295
294
  "requisition_id"
296
295
  ):
@@ -316,10 +315,8 @@ class ModelToDataframe:
316
315
  else:
317
316
  raise
318
317
  for name in self.sys_field_names:
319
- try:
318
+ with contextlib.suppress(ValueError):
320
319
  columns_list.remove(name)
321
- except ValueError:
322
- pass
323
320
  if not self.decrypt:
324
321
  columns_list = [col for col in columns_list if col not in self.encrypted_columns]
325
322
  return columns_list
@@ -338,7 +335,6 @@ class ModelToDataframe:
338
335
  @property
339
336
  def list_columns(self) -> list[str]:
340
337
  """Return a list of column names with fk to a list model."""
341
- from edc_list_data.model_mixins import ListModelMixin, ListUuidModelMixin
342
338
 
343
339
  if not self._list_columns:
344
340
  list_columns = []
@@ -348,14 +344,13 @@ class ModelToDataframe:
348
344
  and fld_cls.related_model
349
345
  and issubclass(fld_cls.related_model, (ListModelMixin, ListUuidModelMixin))
350
346
  ):
351
- list_columns.append(fld_cls.attname)
347
+ list_columns.append(fld_cls.attname) # noqa: PERF401
352
348
  self._list_columns = list(set(list_columns))
353
349
  return self._list_columns
354
350
 
355
351
  @property
356
352
  def site_columns(self) -> list[str]:
357
353
  """Return a list of column names with fk to a site model."""
358
- from django.contrib.sites.models import Site
359
354
 
360
355
  if not self._site_columns:
361
356
  site_columns = []
@@ -372,10 +367,6 @@ class ModelToDataframe:
372
367
  @property
373
368
  def other_columns(self) -> list[str]:
374
369
  """Return other column names with fk to a common models."""
375
- from django.contrib.sites.models import Site
376
-
377
- from edc_lab.models import Panel
378
-
379
370
  related_model = [Site, Panel]
380
371
  if not self._list_columns:
381
372
  list_columns = []
@@ -7,7 +7,7 @@ __all__ = ["read_frame_edc"]
7
7
 
8
8
 
9
9
  def read_frame_edc(
10
- queryset: QuerySet | str = None,
10
+ queryset: [QuerySet] | str | None = None,
11
11
  drop_sys_columns: bool | None = None,
12
12
  drop_action_item_columns: bool | None = None,
13
13
  read_frame_verbose: bool | None = None,
@@ -45,7 +45,7 @@ class ScheduleNameError(Exception):
45
45
  pass
46
46
 
47
47
 
48
- class AlreadyRegisteredVisit(Exception):
48
+ class AlreadyRegisteredVisit(Exception): # noqa: N818
49
49
  pass
50
50
 
51
51
 
@@ -69,15 +69,15 @@ class Schedule:
69
69
 
70
70
  def __init__(
71
71
  self,
72
- name=None,
73
- verbose_name: str = None,
74
- onschedule_model: str = None,
75
- offschedule_model: str = None,
76
- loss_to_followup_model: str = None,
77
- appointment_model: str | None = None,
78
- history_model: str | None = None,
72
+ name: str,
73
+ onschedule_model: str,
74
+ offschedule_model: str,
79
75
  consent_definitions: list[ConsentDefinition] | ConsentDefinition = None,
76
+ loss_to_followup_model: str | None = None,
77
+ appointment_model: str | None = None,
80
78
  offstudymedication_model: str | None = None,
79
+ history_model: str | None = None,
80
+ verbose_name: str | None = None,
81
81
  sequence: str | None = None,
82
82
  base_timepoint: float | Decimal | None = None,
83
83
  ):
@@ -146,10 +146,10 @@ class Schedule:
146
146
 
147
147
  def visits_for_subject(
148
148
  self,
149
- subject_identifier: str = None,
150
- report_datetime: datetime = None,
151
- site_id: int = None,
152
- consent_definition: ConsentDefinition = None,
149
+ subject_identifier: str,
150
+ report_datetime: datetime,
151
+ site_id: int | None = None,
152
+ consent_definition: ConsentDefinition | None = None,
153
153
  ) -> VisitCollection:
154
154
  """Returns a deep copy of visits collection filtered for a
155
155
  given consented subject.
@@ -352,7 +352,7 @@ class Schedule:
352
352
 
353
353
  def get_consent_definition(
354
354
  self,
355
- report_datetime: datetime = None,
355
+ report_datetime: datetime,
356
356
  site: SingleSite = None,
357
357
  consent_definition: ConsentDefinition = None,
358
358
  ) -> ConsentDefinition: