clinicedc 2.0.15__py3-none-any.whl → 2.0.16__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.16
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
@@ -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.16.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
3410
+ clinicedc-2.0.16.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
3411
+ clinicedc-2.0.16.dist-info/METADATA,sha256=yACQMEfCdkmBMoz9yz7R4jaTs-ToDfcahB4QqGBUqII,15899
3412
+ clinicedc-2.0.16.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)
@@ -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: