plain 0.14.1__py3-none-any.whl → 0.16.0__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.
@@ -9,6 +9,12 @@ class DefaultHeadersMiddleware:
9
9
  response = self.get_response(request)
10
10
 
11
11
  for header, value in settings.DEFAULT_RESPONSE_HEADERS.items():
12
+ # Since we don't have a good way to *remote* default response headers,
13
+ # use allow users to set them to an empty string to indicate they should be removed.
14
+ if header in response.headers and response.headers[header] == "":
15
+ del response.headers[header]
16
+ continue
17
+
12
18
  response.headers.setdefault(header, value)
13
19
 
14
20
  # Add the Content-Length header to non-streaming responses if not
plain/utils/timesince.py CHANGED
@@ -4,59 +4,93 @@ from plain.utils.html import avoid_wrapping
4
4
  from plain.utils.text import pluralize_lazy
5
5
  from plain.utils.timezone import is_aware
6
6
 
7
- TIME_STRINGS = {
8
- "year": pluralize_lazy("%(num)d year", "%(num)d years", "num"),
9
- "month": pluralize_lazy("%(num)d month", "%(num)d months", "num"),
10
- "week": pluralize_lazy("%(num)d week", "%(num)d weeks", "num"),
11
- "day": pluralize_lazy("%(num)d day", "%(num)d days", "num"),
12
- "hour": pluralize_lazy("%(num)d hour", "%(num)d hours", "num"),
13
- "minute": pluralize_lazy("%(num)d minute", "%(num)d minutes", "num"),
14
- }
15
7
 
16
- TIME_STRINGS_KEYS = list(TIME_STRINGS.keys())
17
-
18
- TIME_CHUNKS = [
19
- 60 * 60 * 24 * 7, # week
20
- 60 * 60 * 24, # day
21
- 60 * 60, # hour
22
- 60, # minute
23
- ]
24
-
25
- MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
26
-
27
-
28
- def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
8
+ def timesince(
9
+ d: datetime.datetime,
10
+ *,
11
+ now: datetime.datetime | None = None,
12
+ reversed: bool = False,
13
+ format: str | dict[str, str] = "verbose",
14
+ depth: int = 2,
15
+ ) -> str:
29
16
  """
30
17
  Take two datetime objects and return the time between d and now as a nicely
31
- formatted string, e.g. "10 minutes". If d occurs after now, return
32
- "0 minutes".
18
+ formatted string, e.g., "10 minutes" or "10m" (depending on the format).
19
+
20
+ `format` can be:
21
+ - "verbose": e.g., "1 year, 2 months"
22
+ - "short": e.g., "1y 2m"
23
+ - A custom dictionary defining time unit formats.
33
24
 
34
25
  Units used are years, months, weeks, days, hours, and minutes.
35
26
  Seconds and microseconds are ignored.
36
27
 
37
28
  The algorithm takes into account the varying duration of years and months.
38
- There is exactly "1 year, 1 month" between 2013/02/10 and 2014/03/10,
39
- but also between 2007/08/10 and 2008/09/10 despite the delta being 393 days
40
- in the former case and 397 in the latter.
29
+ For example, there is exactly "1 year, 1 month" between 2013/02/10 and
30
+ 2014/03/10, but also between 2007/08/10 and 2008/09/10 despite the delta
31
+ being 393 days in the former case and 397 in the latter.
41
32
 
42
- Up to `depth` adjacent units will be displayed. For example,
33
+ Up to `depth` adjacent units will be displayed. For example,
43
34
  "2 weeks, 3 days" and "1 year, 3 months" are possible outputs, but
44
35
  "2 weeks, 3 hours" and "1 year, 5 days" are not.
45
36
 
46
- `time_strings` is an optional dict of strings to replace the default
47
- TIME_STRINGS dict.
48
-
49
- `depth` is an optional integer to control the number of adjacent time
50
- units returned.
51
-
52
- Originally adapted from
53
- https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
54
- Modified to improve results for years and months.
37
+ Arguments:
38
+ d: A datetime object representing the starting time.
39
+ now: A datetime object representing the current time. Defaults to the
40
+ current time if not provided.
41
+ reversed: If True, calculates time until `d` rather than since `d`.
42
+ format: The output format, either "verbose", "short", or a custom
43
+ dictionary of time unit formats.
44
+ depth: An integer specifying the number of adjacent time units to display.
45
+
46
+ Returns:
47
+ A string representing the time difference, formatted according to the
48
+ specified format.
49
+
50
+ Raises:
51
+ ValueError: If depth is less than 1 or if format is invalid.
55
52
  """
56
- if time_strings is None:
57
- time_strings = TIME_STRINGS
53
+ TIME_CHUNKS = [
54
+ 60 * 60 * 24 * 7, # week
55
+ 60 * 60 * 24, # day
56
+ 60 * 60, # hour
57
+ 60, # minute
58
+ ]
59
+ MONTHS_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
60
+ TIME_STRINGS_KEYS = ["year", "month", "week", "day", "hour", "minute"]
61
+
62
+ VERBOSE_TIME_STRINGS = {
63
+ "year": pluralize_lazy("%(num)d year", "%(num)d years", "num"),
64
+ "month": pluralize_lazy("%(num)d month", "%(num)d months", "num"),
65
+ "week": pluralize_lazy("%(num)d week", "%(num)d weeks", "num"),
66
+ "day": pluralize_lazy("%(num)d day", "%(num)d days", "num"),
67
+ "hour": pluralize_lazy("%(num)d hour", "%(num)d hours", "num"),
68
+ "minute": pluralize_lazy("%(num)d minute", "%(num)d minutes", "num"),
69
+ }
70
+ SHORT_TIME_STRINGS = {
71
+ "year": "%(num)dy",
72
+ "month": "%(num)dmo",
73
+ "week": "%(num)dw",
74
+ "day": "%(num)dd",
75
+ "hour": "%(num)dh",
76
+ "minute": "%(num)dm",
77
+ }
78
+
79
+ # Determine time_strings based on format
80
+ if format == "verbose":
81
+ time_strings = VERBOSE_TIME_STRINGS
82
+ elif format == "short":
83
+ time_strings = SHORT_TIME_STRINGS
84
+ elif isinstance(format, dict):
85
+ time_strings = format
86
+ else:
87
+ raise ValueError(
88
+ "format must be 'verbose', 'short', or a custom dictionary of formats."
89
+ )
90
+
58
91
  if depth <= 0:
59
92
  raise ValueError("depth must be greater than 0.")
93
+
60
94
  # Convert datetime.date to datetime.datetime for comparison.
61
95
  if not isinstance(d, datetime.datetime):
62
96
  d = datetime.datetime(d.year, d.month, d.day)
@@ -82,8 +116,6 @@ def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
82
116
  years, months = divmod(total_months, 12)
83
117
 
84
118
  # Calculate the remaining time.
85
- # Create a "pivot" datetime shifted from d by years and months, then use
86
- # that to determine the other parts.
87
119
  if years or months:
88
120
  pivot_year = d.year + years
89
121
  pivot_month = d.month + months
@@ -131,8 +163,13 @@ def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
131
163
  return ", ".join(result)
132
164
 
133
165
 
134
- def timeuntil(d, now=None, time_strings=None, depth=2):
166
+ def timeuntil(
167
+ d: datetime.datetime,
168
+ now: datetime.datetime | None = None,
169
+ format: str | dict[str, str] = "verbose",
170
+ depth: int = 2,
171
+ ) -> str:
135
172
  """
136
173
  Like timesince, but return a string measuring the time until the given time.
137
174
  """
138
- return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth)
175
+ return timesince(d, now=now, reversed=True, format=format, depth=depth)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.14.1
3
+ Version: 0.16.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -51,7 +51,7 @@ plain/internal/handlers/base.py,sha256=I6549DBZUb-anqox7arzL9wpypH4bUCxZkitO76hl
51
51
  plain/internal/handlers/exception.py,sha256=DH9gh1FyqgetFpMaB8yLIVE6phBTVPKQLA1GIn9MOeI,4555
52
52
  plain/internal/handlers/wsgi.py,sha256=EWoH9EeetfgiL0BtoAe2Aof0VWaBrxl74Y9EP4xx0wc,7553
53
53
  plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- plain/internal/middleware/headers.py,sha256=UnJWnlVVLD-10h7PB_QSYeREBzLo-TS3C-_ahmZ6w0I,636
54
+ plain/internal/middleware/headers.py,sha256=naYsFK0phMGFf1DlRj5fAYz78R5cXToCJfuqm0cR0nI,965
55
55
  plain/internal/middleware/https.py,sha256=XpuQK8HicYX1jNanQHqNgyQ9rqe4NLUOZO3ZzKdsP8k,1203
56
56
  plain/internal/middleware/slash.py,sha256=1rZsSSzXAA-vBb-dc2RlZaKW9gTT8YM8fJzwYGL4swA,2575
57
57
  plain/logs/README.md,sha256=H6uVXdInYlasq0Z1WnhWnPmNwYQoZ1MSLPDQ4ZE7u4A,492
@@ -125,7 +125,7 @@ plain/utils/module_loading.py,sha256=CWl7Shoax9Zkevf1pM9PpS_0V69J5Cukjyj078UPCAw
125
125
  plain/utils/regex_helper.py,sha256=pAdh_xG52BOyXLsiuIMPFgduUAoWOEje1ZpjhcefxiA,12769
126
126
  plain/utils/safestring.py,sha256=SHGhpbX6FFDKSYOY9zYAgAQX0g0exzRba7dM2bJalWs,1873
127
127
  plain/utils/text.py,sha256=qX7vECGH4Xk96qZRH9A1IyZA-mrJ-j62j3kDcLTdWK0,16586
128
- plain/utils/timesince.py,sha256=d-9w_fqKS24IrHsh9-UX6SPNwrQvDp6QhZ_aJzM6AKY,4749
128
+ plain/utils/timesince.py,sha256=a_-ZoPK_s3Pt998CW4rWp0clZ1XyK2x04hCqak2giII,5928
129
129
  plain/utils/timezone.py,sha256=6u0sE-9RVp0_OCe0Y1KiYYQoq5THWLokZFQYY8jf78g,6221
130
130
  plain/utils/tree.py,sha256=wdWzmfsgc26YDF2wxhAY3yVxXTixQYqYDKE9mL3L3ZY,4383
131
131
  plain/views/README.md,sha256=qndsXKyNMnipPlLaAvgQeGxqXknNQwlFh31Yxk8rHp8,5994
@@ -138,8 +138,8 @@ plain/views/forms.py,sha256=RhlaUcZCkeqokY_fvv-NOS-kgZAG4XhDLOPbf9K_Zlc,2691
138
138
  plain/views/objects.py,sha256=fRfS6KNehIGqkbPw4nSafj8HStxYExHmbggolBbzcxs,7921
139
139
  plain/views/redirect.py,sha256=KLnlktzK6ZNMTlaEiZpMKQMEP5zeTgGLJ9BIkIJfwBo,1733
140
140
  plain/views/templates.py,sha256=nF9CcdhhjAyp3LB0RrSYnBaHpHzMfPSw719RCdcXk7o,2007
141
- plain-0.14.1.dist-info/METADATA,sha256=TcNf9s_L6irqxS7NOX_4RRSASEuzIBW9kshjZ_DaUg8,2518
142
- plain-0.14.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
143
- plain-0.14.1.dist-info/entry_points.txt,sha256=DHHprvufgd7xypiBiqMANYRnpJ9xPPYhYbnPGwOkWqE,40
144
- plain-0.14.1.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
145
- plain-0.14.1.dist-info/RECORD,,
141
+ plain-0.16.0.dist-info/METADATA,sha256=QYjt6bNbKLzOHA2ER7Mch3hy9f0nrZm03kXJM56hQXs,2518
142
+ plain-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
143
+ plain-0.16.0.dist-info/entry_points.txt,sha256=DHHprvufgd7xypiBiqMANYRnpJ9xPPYhYbnPGwOkWqE,40
144
+ plain-0.16.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
145
+ plain-0.16.0.dist-info/RECORD,,
File without changes