lino 25.4.4__py3-none-any.whl → 25.4.5__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.
lino/__init__.py CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
31
31
  from django.apps import AppConfig
32
32
  from django.conf import settings
33
33
  import warnings
34
- __version__ = '25.4.4'
34
+ __version__ = '25.4.5'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/core/fields.py CHANGED
@@ -163,7 +163,7 @@ class PriceField(models.DecimalField):
163
163
  """
164
164
  A thin wrapper around Django's `DecimalField
165
165
  <https://docs.djangoproject.com/en/5.0/ref/models/fields/#decimalfield>`_
166
- which adds default values for `decimal_places`, `max_length` and
166
+ with price-like default values for `decimal_places`, `max_length` and
167
167
  `max_digits`.
168
168
  """
169
169
 
@@ -656,8 +656,10 @@ class VirtualField(FakeField):
656
656
  self.model = model
657
657
  self.name = name
658
658
  self.attname = name
659
+ # if getattr(self.return_type, "model", False):
659
660
  if hasattr(self.return_type, "model"):
660
- # logger.info("20200425 return_type for virtual field %s has a model", self)
661
+ # logger.info("20200425 return_type for virtual "
662
+ # "field %s has a model %s (not %s)", self, self.return_type.model, model)
661
663
  return
662
664
  self.return_type.model = VirtualModel(model)
663
665
  self.return_type.column = None
lino/core/kernel.py CHANGED
@@ -37,6 +37,7 @@ from django.utils.translation import gettext_lazy as _
37
37
  from django.core.exceptions import PermissionDenied, ValidationError
38
38
  from django.db.utils import DatabaseError
39
39
  from django.db import IntegrityError
40
+ from django.utils.log import DEFAULT_LOGGING
40
41
 
41
42
  from django.db import models
42
43
 
@@ -162,7 +163,9 @@ class Kernel(object):
162
163
  # logger.info("20231016 kernel_startup (%s)", site._history_aware_logging)
163
164
  # print("20231019 kernel_startup()", site, site._history_aware_logging)
164
165
 
165
- if site._history_aware_logging:
166
+ # if site._history_aware_logging:
167
+ # if not is_devserver():
168
+ if "file" in DEFAULT_LOGGING['handlers']:
166
169
  if len(sys.argv) == 0:
167
170
  process_name = "WSGI"
168
171
  else:
lino/core/requests.py CHANGED
@@ -2031,9 +2031,9 @@ class ActionRequest(BaseRequest):
2031
2031
  if w is None:
2032
2032
  WARNINGS_LOGGED[e] = True
2033
2033
  # raise
2034
- # logger.exception(e)
2035
2034
  logger.warning(f"Error while executing {repr(self)}: {e}\n"
2036
2035
  "(Subsequent warnings will be silenced.)")
2036
+ logger.exception(e)
2037
2037
 
2038
2038
  if self._data_iterator is None:
2039
2039
  raise Exception(f"No data iterator for {self}")
lino/core/site.py CHANGED
@@ -36,12 +36,13 @@ from lino.utils.html import E, tostring
36
36
  from lino.core.plugin import Plugin
37
37
  from lino.utils import AttrDict, date_offset, i2d, buildurl
38
38
 
39
+
39
40
  NO_REMOTE_AUTH = True
40
41
  # 20240518 We have only one production site still using remote http
41
42
  # authentication, and they will migrate to sessions-based auth with their next
42
43
  # upgrade.
43
44
 
44
- ASYNC_LOGGING = False
45
+ # ASYNC_LOGGING = False
45
46
  # This is to fix the issue that the "started" and "ended" messages are not logged.
46
47
  # But setting this to True causes #4986 (Unable to configure handler 'mail_admins')
47
48
  # because since 20230529 we called logging..config.dictConfig() during
@@ -368,14 +369,14 @@ class Site(object):
368
369
  csv_params = dict()
369
370
 
370
371
  # attributes documented in book/docs/opics/loggin.rst:
371
- _history_aware_logging = False
372
+ _history_aware_logging = True
372
373
  log_each_action_request = False
374
+ default_loglevel = "INFO"
373
375
  logger_filename = "lino.log"
374
376
  logger_format = (
375
377
  "%(asctime)s %(levelname)s [%(name)s %(process)d %(thread)d] : %(message)s"
376
378
  )
377
379
  auto_configure_logger_names = "atelier lino"
378
- log_sock_path = None
379
380
 
380
381
  # appy_params = dict(ooPort=8100)
381
382
  appy_params = dict(
@@ -597,31 +598,36 @@ class Site(object):
597
598
  self.startup_time = datetime.datetime.now()
598
599
 
599
600
  def setup_logging(self):
600
- # documented in book/docs/opics/loggin.rst
601
+ # documented in book/docs/topics/logging.rst
601
602
 
602
- if not self.auto_configure_logger_names:
603
+ if self.auto_configure_logger_names is None:
603
604
  return
604
605
 
605
- if len(logging.root.handlers) > 0:
606
- # Logging has been configured by something else. This can happen
607
- # when Site is instantiated a second time. Or accidentaly (e.g. when you call logging.basicConfig() in the settings.py), Or when some testing
608
- # environment runs multiple doctests in a same process. We don't
609
- # care, we restart configuration from scratch.
610
-
611
- for handler in logging.root.handlers[:]:
612
- logging.root.removeHandler(handler)
606
+ # if len(logging.root.handlers) > 0:
607
+ #
608
+ # # Logging has been configured by something else. This can happen
609
+ # # when Site is instantiated a second time. Or accidentaly (e.g. when
610
+ # # you call logging.basicConfig() in the settings.py), Or when some
611
+ # # testing environment runs multiple doctests in a same process. We
612
+ # # don't care, we restart configuration from scratch.
613
+ #
614
+ # for handler in logging.root.handlers[:]:
615
+ # logging.root.removeHandler(handler)
613
616
 
614
617
  from django.utils.log import DEFAULT_LOGGING
615
618
 
616
619
  d = DEFAULT_LOGGING
617
620
 
618
- if d.get("logger_ok", False):
619
- # raise Exception("20231017")
620
- return
621
+ # if d.get("logger_ok", False):
622
+ # # raise Exception("20231017")
623
+ # return
621
624
 
622
- level = os.environ.get("LINO_LOGLEVEL", "INFO").upper()
625
+ level = os.environ.get("LINO_LOGLEVEL", self.default_loglevel).upper()
623
626
  file_level = os.environ.get("LINO_FILE_LOGLEVEL", level).upper()
624
- min_level = min(*[getattr(logging, k) for k in (level, file_level)])
627
+ sql_level = os.environ.get("LINO_SQL_LOGLEVEL", level).upper()
628
+
629
+ min_level = min(*[getattr(logging, k) for k in (
630
+ level, file_level, sql_level)])
625
631
 
626
632
  # print("20231017 level is", level)
627
633
 
@@ -642,22 +648,13 @@ class Site(object):
642
648
 
643
649
  # when Site is instantiated several times, we keep the existing file handler
644
650
  # print("20231016", self.logger_filename, handlers.keys())
645
- if self.logger_filename and "file" not in handlers:
651
+ if "file" not in handlers:
646
652
  logdir = self.site_dir / "log"
647
653
  if logdir.is_dir():
648
654
  self._history_aware_logging = True
649
655
  log_file_path = logdir / self.logger_filename
650
656
  # print("20231019 logging", file_level, "to", log_file_path)
651
- self.log_sock_path = logdir / (self.logger_filename + ".sock")
652
- if self.log_sock_path.exists():
653
- # print("20231019 log via socket server")
654
- handlers["file"] = {
655
- "class": "lino.core.site.LinoSocketHandler",
656
- "host": str(self.log_sock_path),
657
- "port": None,
658
- "level": file_level,
659
- }
660
- else:
657
+ if True:
661
658
  # print("20231019 log directly to file")
662
659
  formatters = d.setdefault("formatters", {})
663
660
  formatters.setdefault(
@@ -671,6 +668,15 @@ class Site(object):
671
668
  "encoding": "UTF-8",
672
669
  "formatter": "verbose",
673
670
  }
671
+ else:
672
+ try:
673
+ from systemd.journal import JournalHandler
674
+ handlers["file"] = {
675
+ "class": "systemd.journal.JournalHandler",
676
+ "SYSLOG_IDENTIFIER": str(self.project_name),
677
+ }
678
+ except ImportError:
679
+ pass
674
680
 
675
681
  # when a file handler exists, we have the loggers use it even if this
676
682
  # instance didn't create it:
@@ -681,9 +687,10 @@ class Site(object):
681
687
  # if name not in d['loggers']:
682
688
  d["loggers"][name] = loggercfg
683
689
 
684
- dblogger = d["loggers"].setdefault("django.db.backends", {})
685
- dblogger["level"] = os.environ.get("LINO_SQL_LOGLEVEL", "WARNING")
686
- dblogger["handlers"] = loggercfg["handlers"]
690
+ if sql_level != level:
691
+ dblogger = d["loggers"].setdefault("django.db.backends", {})
692
+ dblogger["level"] = sql_level
693
+ dblogger["handlers"] = loggercfg["handlers"]
687
694
 
688
695
  # # https://code.djangoproject.com/ticket/30554
689
696
  # logger = d['loggers'].setdefault('django.utils.autoreload', {})
@@ -694,19 +701,19 @@ class Site(object):
694
701
  # if item not in ['linod', 'root']:
695
702
  # d['loggers'][item]['propagate'] = True
696
703
 
697
- if ASYNC_LOGGING:
698
- config = d.copy()
699
-
700
- try:
701
- logging.config.dictConfig(config)
702
- # logging.config.dictConfig(d)
703
- finally:
704
- d.clear()
705
- d["logger_ok"] = True
706
- d["version"] = 1
707
- d["disable_existing_loggers"] = False
708
- else:
709
- d["logger_ok"] = True
704
+ # if ASYNC_LOGGING:
705
+ # config = d.copy()
706
+ #
707
+ # try:
708
+ # logging.config.dictConfig(config)
709
+ # # logging.config.dictConfig(d)
710
+ # finally:
711
+ # d.clear()
712
+ # # d["logger_ok"] = True
713
+ # d["version"] = 1
714
+ # d["disable_existing_loggers"] = False
715
+ # else:
716
+ # d["logger_ok"] = True
710
717
  # self.update_settings(LOGGING=d)
711
718
  # pprint(d)
712
719
  # print("20161126 Site %s " % d['loggers'].keys())
lino/core/utils.py CHANGED
@@ -37,6 +37,9 @@ get_models = apps.get_models
37
37
 
38
38
  validate_url = URLValidator()
39
39
 
40
+ DEVSERVER_COMMANDS = {
41
+ "runserver", "testserver", "test", "demotest", "makescreenshots", "shell"}
42
+
40
43
 
41
44
  def djangoname(o):
42
45
  return o.__module__.split(".")[-2] + "." + o.__name__
@@ -144,6 +147,8 @@ def is_devserver():
144
147
 
145
148
  """
146
149
  # ~ print 20130315, sys.argv[1]
150
+ if settings.DEBUG:
151
+ return True # doctest under pytest
147
152
  if sys.argv[0].startswith("-"):
148
153
  return True # doctest under pytest
149
154
  if len(sys.argv) <= 1:
@@ -153,9 +158,9 @@ def is_devserver():
153
158
  # if sys.argv[0].endswith("doctest.py") or sys.argv[0].endswith("doctest_utf8.py"):
154
159
  if sys.argv[0].endswith("doctest.py") or sys.argv[0].endswith("pytest"):
155
160
  return True
156
- if sys.argv[1] in ("runserver", "testserver", "test", "makescreenshots", "shell"):
161
+ if sys.argv[1] in DEVSERVER_COMMANDS:
157
162
  return True
158
- # print(sys.argv[1])
163
+ # print(sys.argv)
159
164
  return False
160
165
 
161
166
 
lino/help_texts.py CHANGED
@@ -182,8 +182,6 @@ help_texts = {
182
182
  'lino.utils.IncompleteDate' : _("""Naive representation of a potentially incomplete gregorian date."""),
183
183
  'lino.utils.IncompleteDate.parse' : _("""Parse the given string and return an IncompleteDate object."""),
184
184
  'lino.utils.IncompleteDate.get_age' : _("""Return age in years as integer."""),
185
- 'lino.utils.SumCollector' : _("""A dictionary of sums to be collected using an arbitrary key."""),
186
- 'lino.utils.SumCollector.collect' : _("""Add the given value to the sum at the given key k."""),
187
185
  'lino.utils.MissingRow' : _("""Represents a database row that is expected to exist but doesn’t."""),
188
186
  'lino.utils.addressable.Addressable' : _("""General mixin (not only for Django models) to encapsulate the generating of “traditional” (“snail”) mail addresses."""),
189
187
  'lino.utils.addressable.Addressable.address_person_lines' : _("""Yield one or more text lines, one for each line of the person part."""),
@@ -1,18 +1,18 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2010-2020 Rumma & Ko Ltd
2
+ # Copyright 2010-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """
5
5
  This defines the :class:`Registable` model mixin.
6
6
  """
7
7
 
8
8
  from django.db import models
9
- from django.utils.translation import gettext_lazy as _
9
+ # from django.utils.translation import gettext_lazy as _
10
10
 
11
+ from lino import logger
11
12
  from lino.core import model
12
- from lino.core.actions import Action
13
+ # from lino.core.actions import Action
13
14
  from lino.core.workflows import ChangeStateAction
14
15
  from lino.core.exceptions import ChangedAPI
15
-
16
16
  from lino.core.workflows import State
17
17
 
18
18
 
@@ -180,6 +180,7 @@ class Registrable(model.Model):
180
180
  state_field = self.workflow_state_field
181
181
  target_state = state_field.choicelist.draft
182
182
  self.set_workflow_state(ar, state_field, target_state)
183
+ logger.warning("%s deregisters %s", ar.get_user(), self)
183
184
 
184
185
  # no longer needed after 20170826
185
186
  # @classmethod
@@ -11,6 +11,7 @@ from django.conf import settings
11
11
  from django.utils.html import mark_safe, escape
12
12
 
13
13
  from lino.api import dd, _
14
+ from lino.utils.sums import myround
14
15
  from lino.utils.xml import validate_xml
15
16
  from lino.utils.media import MediaFile
16
17
 
@@ -51,6 +52,7 @@ class XMLMaker(dd.Model):
51
52
  context = self.get_printable_context(ar)
52
53
  context.update(xml_element=xml_element)
53
54
  context.update(base64=base64)
55
+ context.update(myround=myround)
54
56
  xml = tpl.render(**context)
55
57
  # parts = [
56
58
  # dd.plugins.accounting.xml_media_dir,
@@ -3,13 +3,11 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  # import time
6
- import os
7
6
  import asyncio
8
-
9
7
  from django.conf import settings
10
8
  from django.core.management import BaseCommand, call_command
11
- from lino.api import dd, rt
12
- from lino.modlib.linod.mixins import start_log_server, start_task_runner
9
+ from lino.api import dd
10
+ from lino.modlib.linod.mixins import start_task_runner
13
11
  from lino.core.requests import BaseRequest
14
12
 
15
13
  if dd.plugins.linod.use_channels:
@@ -33,19 +31,6 @@ class Command(BaseCommand):
33
31
  # default=False)
34
32
 
35
33
  def handle(self, *args, **options):
36
- log_sock_path = settings.SITE.log_sock_path
37
-
38
- if log_sock_path and log_sock_path.exists():
39
- if options.get("force"):
40
- log_sock_path.unlink()
41
- else:
42
- raise Exception(
43
- f"log socket already exists: {log_sock_path}\n"
44
- "It's probable that a worker process is already running. "
45
- "Try: 'ps awx | grep linod' OR 'sudo supervisorctl status | grep worker'\n"
46
- "Or the last instance of the worker process did not finish properly. "
47
- "In that case remove the file and run this command again."
48
- )
49
34
 
50
35
  if not dd.plugins.linod.use_channels:
51
36
  # print("20240424 Run Lino daemon without channels")
@@ -57,8 +42,8 @@ class Command(BaseCommand):
57
42
  except settings.SITE.user_model.DoesNotExist:
58
43
  u = None
59
44
  ar = BaseRequest(user=u)
60
- # ar = rt.login(dd.plugins.linod.daemon_user)
61
- await asyncio.gather(start_log_server(), start_task_runner(ar))
45
+ # await asyncio.gather(start_log_server(), start_task_runner(ar))
46
+ await start_task_runner(ar)
62
47
  # t1 = asyncio.create_task(settings.SITE.start_log_server())
63
48
  # t2 = asyncio.create_task(start_task_runner(ar))
64
49
  # await t1
@@ -84,7 +69,7 @@ class Command(BaseCommand):
84
69
  async def initiate_linod():
85
70
  layer = get_channel_layer()
86
71
  # if log_sock_path is not None:
87
- await layer.send(CHANNEL_NAME, {"type": "log.server"})
72
+ # await layer.send(CHANNEL_NAME, {"type": "log.server"})
88
73
  # await asyncio.sleep(1)
89
74
  await layer.send(CHANNEL_NAME, {"type": "run.background.tasks"})
90
75
 
@@ -291,42 +291,3 @@ async def start_task_runner(ar=None, max_count=None):
291
291
  continue
292
292
  await ar.adebug("Let task runner sleep for %s seconds.", to_sleep)
293
293
  await asyncio.sleep(to_sleep)
294
-
295
-
296
- class LogReceiver(asyncio.Protocol):
297
- # def connection_made(self, transport):
298
- # print("20231019 connection_made", transport)
299
-
300
- def data_received(self, data: bytes):
301
- data = pickle.loads(
302
- data[4:]
303
- ) # first four bytes gives the size of the rest of the data
304
- record = logging.makeLogRecord(data)
305
- # print("20231019 data_received", record)
306
- # 20231019 server_logger.handle(record)
307
- logger.handle(record)
308
-
309
-
310
- async def start_log_server():
311
- # 'log.server' in linod.py
312
- site = settings.SITE
313
- log_sock_path = site.log_sock_path
314
- if log_sock_path is None:
315
- logger.info(
316
- "No log server because there is no directory %s.", site.site_dir / "log"
317
- )
318
- return
319
- if log_sock_path.exists():
320
- raise Exception("Cannot start log server when socket file exists.")
321
- logger.info("Log server starts listening on %s", log_sock_path)
322
-
323
- def remove_sock_file():
324
- logger.info("Remove socket file %s", site.log_sock_path)
325
- site.log_sock_path.unlink(missing_ok=True)
326
-
327
- site.register_shutdown_task(remove_sock_file)
328
- loop = asyncio.get_running_loop()
329
- server = await loop.create_unix_server(LogReceiver, log_sock_path)
330
- # await server.serve_forever()
331
- async with server:
332
- await server.serve_forever()
@@ -6,6 +6,12 @@
6
6
  {% if isinstance(obj, rt.models.publisher.Page) -%}
7
7
  {{obj.get_prev_link(ar)}} | {{obj.get_next_link(ar)}} |
8
8
  {% endif -%}
9
+ {% if dd.is_installed('react') %}
10
+ {% set ar_react = obj.get_default_table().request(parent=ar, permalink_uris=True, renderer=dd.plugins.react.renderer) %}
11
+ {% if ar_react.obj2url(obj) %}
12
+ {{ tostring(ar_react.obj2html(obj, text='Edit')) }} |
13
+ {% endif %}
14
+ {% endif %}
9
15
  {% if ar.request and ar.request.path != '/' -%}
10
16
  <a href="{{ar.get_home_url()}}">{{_("Home")}}</a>
11
17
  {% else -%}
@@ -14,4 +14,17 @@ home_children = [
14
14
 
15
15
 
16
16
  def objects():
17
- return rt.models.publisher.make_demo_pages(home_children)
17
+ image = rt.models.uploads.Upload.objects.first()
18
+ print('='*80)
19
+ print(image)
20
+ def iterate(iterable):
21
+ try:
22
+ for obj in iterable:
23
+ yield iterate(obj)
24
+ except TypeError:
25
+ if (obj := iterable).title == 'Mission':
26
+ obj.main_image = image
27
+ yield obj
28
+ for obj in rt.models.publisher.make_demo_pages(home_children):
29
+ yield iterate(obj)
30
+
@@ -9,10 +9,13 @@ from django.conf import settings
9
9
  from django.utils import translation
10
10
  from django.utils.translation import pgettext_lazy
11
11
 
12
- from lorem import get_paragraph
13
12
  from django.utils import translation
14
13
  from django.conf import settings
15
14
 
15
+ try:
16
+ from lorem import get_paragraph
17
+ except ImportError:
18
+ lorem = None
16
19
 
17
20
  # from django.utils.translation import get_language
18
21
  from django.utils.html import mark_safe
@@ -160,16 +163,6 @@ class Page(
160
163
 
161
164
  # if not self.is_public():
162
165
  # return
163
- if hlevel == 1 and self.main_image:
164
- yield f"""
165
- <div class="row">
166
- <div class="center-block">
167
- <a href="#" class="thumbnail">
168
- <img src="{self.main_image.get_media_file().get_image_url()}">
169
- </a>
170
- </div>
171
- </div>
172
- """
173
166
 
174
167
  if display_mode in ("detail",):
175
168
  info = self.get_node_info(ar)
@@ -182,9 +175,20 @@ class Page(
182
175
 
183
176
  # if display_mode in ("detail", "story"):
184
177
  if display_mode == "detail":
185
- if hlevel == 1 and not dd.plugins.memo.use_markup:
178
+ if hlevel == 1 and not dd.plugins.memo.use_markup and self.ref != 'index':
186
179
  yield self.toc_html(ar)
187
180
 
181
+ if hlevel == 1 and self.main_image:
182
+ yield f"""
183
+ <div class="row">
184
+ <div class="center-block">
185
+ <a href="#" class="thumbnail">
186
+ <img src="{self.main_image.get_media_file().get_image_url()}">
187
+ </a>
188
+ </div>
189
+ </div>
190
+ """
191
+
188
192
  # yield self.body_full_preview
189
193
  yield self.get_body_parsed(ar, short=False)
190
194
 
lino/utils/__init__.py CHANGED
@@ -63,7 +63,6 @@ import traceback
63
63
  from dateutil.relativedelta import relativedelta
64
64
  import re
65
65
  from decimal import Decimal
66
- from collections import OrderedDict
67
66
  from urllib.parse import urlencode
68
67
 
69
68
  # import locale
@@ -76,6 +75,7 @@ from etgen.utils import join_elems
76
75
 
77
76
  from lino.utils.cycler import Cycler
78
77
  from lino.utils.code import codefiles, codetime
78
+ from .sums import SumCollector
79
79
 
80
80
  from rstgen.utils import confirm, i2d, i2t
81
81
 
@@ -609,61 +609,6 @@ def puts(s):
609
609
  print(s)
610
610
 
611
611
 
612
- class SumCollector(object):
613
- """A dictionary of sums to be collected using an arbitrary key.
614
-
615
- This is also included in the default context used by the Jinja
616
- renderer (:mod:`lino.modlib.jinja`) when rendering templates,
617
- which makes it a more complete solution for a problem asked also
618
- elsewhere, e.g. on `Stackoverflow
619
- <https://stackoverflow.com/questions/7537439/how-to-increment-a-variable-on-a-for-loop-in-jinja-template>`__.
620
-
621
- See examples in :doc:`/topics/utils`.
622
-
623
- """
624
-
625
- def __init__(self):
626
- self._sums = OrderedDict()
627
-
628
- def collect(self, k, value):
629
- """Add the given value to the sum at the given key k."""
630
- if value is None:
631
- return
632
- if k in self._sums:
633
- # print("20230614 a", k, "=", self._sums[k], "+", value)
634
- self._sums[k] += value
635
- else:
636
- # print("20230614 b", k, "=", value)
637
- self._sums[k] = value
638
-
639
- def __getattr__(self, k):
640
- return self._sums.get(k)
641
-
642
- def __getitem__(self, k):
643
- return self._sums.get(k)
644
-
645
- def total(self):
646
- return sum(self._sums.values())
647
-
648
- def items(self, *args, **kwargs):
649
- return self._sums.items(*args, **kwargs)
650
-
651
- def keys(self, *args, **kwargs):
652
- return self._sums.keys(*args, **kwargs)
653
-
654
- def values(self, *args, **kwargs):
655
- return self._sums.values(*args, **kwargs)
656
-
657
- def __len__(self):
658
- return self._sums.__len__()
659
-
660
- def __str__(self):
661
- return str(self._sums)
662
-
663
- def __repr__(self):
664
- return repr(self._sums)
665
-
666
-
667
612
  class SimpleSingleton(object):
668
613
  _instance = None
669
614
 
lino/utils/sums.py ADDED
@@ -0,0 +1,75 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2012-2025 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+ # Documentation: https://dev.lino-framework.org/plugins/accounting.html
5
+
6
+ from collections import OrderedDict
7
+ from decimal import Decimal, ROUND_HALF_UP
8
+
9
+ ZERO = Decimal(0)
10
+ CENT = Decimal('.01')
11
+ HUNDRED = Decimal('100.00')
12
+ ONE = Decimal('1.00')
13
+ MAX_AMOUNT = Decimal("9999999.00")
14
+
15
+
16
+ def myround(d):
17
+ return d.quantize(CENT, rounding=ROUND_HALF_UP)
18
+
19
+
20
+ class SumCollector:
21
+ """A dictionary of sums to be collected using an arbitrary key.
22
+
23
+ This is also included in the default context used by the Jinja
24
+ renderer (:mod:`lino.modlib.jinja`) when rendering templates,
25
+ which makes it a more complete solution for a problem asked also
26
+ elsewhere, e.g. on `Stackoverflow
27
+ <https://stackoverflow.com/questions/7537439/how-to-increment-a-variable-on-a-for-loop-in-jinja-template>`__.
28
+
29
+ See examples in :doc:`/topics/utils`.
30
+
31
+ """
32
+
33
+ def __init__(self):
34
+ self._sums = OrderedDict()
35
+
36
+ def collect(self, k, value):
37
+ """Add the given value to the sum at the given key k."""
38
+ if value is None:
39
+ return
40
+ if k in self._sums:
41
+ # print("20230614 a", k, "=", self._sums[k], "+", value)
42
+ self._sums[k] += value
43
+ else:
44
+ # print("20230614 b", k, "=", value)
45
+ self._sums[k] = value
46
+
47
+ def myround(self):
48
+ self._sums = {k: myround(v) for k, v in self._sums.items()}
49
+
50
+ def __getattr__(self, k):
51
+ return self._sums.get(k)
52
+
53
+ def __getitem__(self, k):
54
+ return self._sums.get(k)
55
+
56
+ def total(self):
57
+ return sum(self._sums.values())
58
+
59
+ def items(self, *args, **kwargs):
60
+ return self._sums.items(*args, **kwargs)
61
+
62
+ def keys(self, *args, **kwargs):
63
+ return self._sums.keys(*args, **kwargs)
64
+
65
+ def values(self, *args, **kwargs):
66
+ return self._sums.values(*args, **kwargs)
67
+
68
+ def __len__(self):
69
+ return self._sums.__len__()
70
+
71
+ def __str__(self):
72
+ return str(self._sums)
73
+
74
+ def __repr__(self):
75
+ return repr(self._sums)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.4.4
3
+ Version: 25.4.5
4
4
  Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
5
5
  Project-URL: Homepage, https://www.lino-framework.org
6
6
  Project-URL: Repository, https://gitlab.com/lino-framework/lino
@@ -1,10 +1,10 @@
1
1
  lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
2
2
  lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
3
- lino/__init__.py,sha256=UvUTsw4pGmG-8l3eqcbQdedQ1jGLYfZdqOF3no5VCZI,6176
3
+ lino/__init__.py,sha256=BwMhah1-EWVulbl6pSZkJqaPAx15Bc3Lg58OFimYFqI,6176
4
4
  lino/ad.py,sha256=AQ-vJ4scac1mx3xegXezxnxyOQpV-a0q3VFMJSDbj2s,142
5
5
  lino/apps.py,sha256=ECq-dPARDkuhngwNrcipse3b4Irj70HxJs44uWEZFc4,27
6
6
  lino/hello.py,sha256=7-PJg7PnEiznyETqGjOwXcKh8rda0qLetpbS2gvRYy0,532
7
- lino/help_texts.py,sha256=n0cYycLTZFF72ybnYAwMkF-FxpLPyS4fNxQaUNpNTAQ,91428
7
+ lino/help_texts.py,sha256=z4EGZdwOchg1_ksrjA3oWaQUlFRCxF14JpK4e8WHihY,91224
8
8
  lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
9
9
  lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
10
10
  lino/api/dd.py,sha256=-mxiSJS27LGCWyOgboA8qDudTkE9vg9vmUGOT08Hq6A,7410
@@ -42,12 +42,12 @@ lino/core/ddh.py,sha256=dYScxWKTOCDEgow7wJNJe812ESasmmITPK2ovraBQno,3172
42
42
  lino/core/diff.py,sha256=XQ-oQQDS_v3kXd4eRP9Hwr5UCgp-TPZIPVav9ZblUno,5882
43
43
  lino/core/elems.py,sha256=3PZ-0RDr6c8QoZa9sWKV36w-wn1Oj2xFpjW8eHrEpzs,108811
44
44
  lino/core/exceptions.py,sha256=QDxDo5cllSyXQ8VWet9hGXzNadxCOmwMVrFXc6V-vpE,665
45
- lino/core/fields.py,sha256=ZnNMTI_1VbNbfIABa2r3hiOByZ2BF0M4TUOOFZfL72U,58106
45
+ lino/core/fields.py,sha256=zPP90oYxodCyF4DzE62008fUzH75C5RimOtoaWbfRz4,58231
46
46
  lino/core/frames.py,sha256=ISxgq9zyZfqW3tDZMWdKi9Ij455lT_81qBH0xex0bfE,1161
47
47
  lino/core/gfks.py,sha256=6VXn2FSIXOrwVq0stfbPevT37EWg1tg4Fn-HMNVnbmk,1970
48
48
  lino/core/help.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  lino/core/inject.py,sha256=Qd_PGEn0yMXNYVPI0wCv1vvo2CNdlPkyoBmKZELOtGM,13422
50
- lino/core/kernel.py,sha256=oeBQb5rbsSKWPXW67gHRTup5IXKGBptVbPAfiUjHVog,47664
50
+ lino/core/kernel.py,sha256=kKgKmgUZqh7rFqpKjuM_T6pUvaw7K_vl3PEfRqvrg8Q,47794
51
51
  lino/core/keyboard.py,sha256=W3jA6qtB5HMppoNnd_6PgIM7ZlyHilJEhBvdeZTY7Xk,1283
52
52
  lino/core/layouts.py,sha256=Wojx5UUyhy7PsZE8FWmiXcmZDj4wz2y_1_wlz1G560Q,28663
53
53
  lino/core/menus.py,sha256=W0Co9K-ayvfX5Zt5rgSxJrFRejtiYrwIR5EecdYXPNc,10857
@@ -56,16 +56,16 @@ lino/core/model.py,sha256=YENZ-v1sggtdTitgmC8jBmuihN1EsKXQMlbPAuTSat8,36870
56
56
  lino/core/permissions.py,sha256=Fnemz3NwWz21X0YATI9Q7ba2FcAdg-EMLHjIcbt_AVU,6840
57
57
  lino/core/plugin.py,sha256=8FdxFF5tqHK-N8aXANzpWXY97hj6Lty4ulM6PWcq5A8,6833
58
58
  lino/core/renderer.py,sha256=C00J0L41hLv9b2sAzqSSrT0Lz5Fc78JnwHiq9LqF81c,47310
59
- lino/core/requests.py,sha256=xIq6kXhjLoYbSkW40_BH-uj0WvmfkvOVLGm2piiGtJo,95835
59
+ lino/core/requests.py,sha256=aIT8DsWVWwYQWHf7xX7SbbUf0s_8dWfayR3104VDrnQ,95833
60
60
  lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
61
61
  lino/core/signals.py,sha256=0JT89mkjSbRm57QZcSI9DoThoKUGkyi-egNhuLUKEds,948
62
- lino/core/site.py,sha256=WRbYscZ7H8OnYMX0ptxjzNs9R6RvyBUy80f3kETo5Jw,82697
62
+ lino/core/site.py,sha256=WqL_QrimT85POO7wKp3R4s8O4P_tvOOxg6WDO968vOQ,82796
63
63
  lino/core/store.py,sha256=6pd4J5Y-U7Muz4mKFSL6K9KEZpJUbpM-I7RQTWyCo-8,51483
64
64
  lino/core/tables.py,sha256=Yoj-H2ASW9xIger8kuW8xst5A_tEmtWpV17L11Ebca0,24348
65
65
  lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
66
66
  lino/core/user_types.py,sha256=0iSYmzr2M9v2Mn2y6hzAZeqareUT-gD7l3MfIPyG9ZI,867
67
67
  lino/core/userprefs.py,sha256=cmufIS9xJErKDrw05uoWtQTUR6WRWIGkU1KdAqzNr5M,2850
68
- lino/core/utils.py,sha256=TbLCLyse2CjCemzUDC-RzmqXFqFrfPB1Ix-vUV97A9I,36496
68
+ lino/core/utils.py,sha256=6yFnP9BFOW5O4oABdv0ASAeJf6oYQ7BZioEqtBBTVzk,36618
69
69
  lino/core/views.py,sha256=qLjEN6GSXScbmAnKN7yDHySmsjL0h4sMKRIQCpOEdPU,7026
70
70
  lino/core/widgets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  lino/core/workflows.py,sha256=9O5INnx3nPW73UmeEPWgN146clQSWwna3Jq_w1MThcg,10567
@@ -123,7 +123,7 @@ lino/mixins/periods.py,sha256=iFqUCUZlgUW2GmEHRl9suwJedztrXLluQhgFpu7Uj54,11479
123
123
  lino/mixins/polymorphic.py,sha256=MLbfOeIYRoDZO4048X2oWhG5cxds2pLkwciXcw1xjVQ,9393
124
124
  lino/mixins/printable.py,sha256=4U8M1lrTjUeuaPwrcWoanCBo53iAxiNpSTsVctI-gI0,199
125
125
  lino/mixins/ref.py,sha256=ZnyTcpHgcbYwDwgaBwLc5cGLQqz8MmjAypSEgPB7Ql4,5272
126
- lino/mixins/registrable.py,sha256=I9KqbYf0kZdbpD4UBUwNK7_OhRdWQDVqK0tkSU-M2ZU,7114
126
+ lino/mixins/registrable.py,sha256=EqszzywB9LN37zl7NRBHZxsHzFRoBQb-f9HBkSHo0vw,7206
127
127
  lino/mixins/sequenced.py,sha256=6FiHJX2ZIraqUofdnb082b978PISNbpd2Vr87YA_1pg,16460
128
128
  lino/modlib/__init__.py,sha256=cO31gNu2oRkp7o2v3D9gK2H7j4jF9rbVyPxPZhZQwrQ,940
129
129
  lino/modlib/about/__init__.py,sha256=jhqGQIXU1o7KkmmQjfwPKJc3buibB09Fy55carAmi7U,342
@@ -3517,7 +3517,7 @@ lino/modlib/ipdict/models.py,sha256=9-pjj_xXdndhQaQI8sgXcmODxurST_aFcShGwomiYKk,
3517
3517
  lino/modlib/jinja/__init__.py,sha256=XSa-e1qZGabl8EmRDKPRtmzgBRole5ZbxAUBR_m-ds0,3418
3518
3518
  lino/modlib/jinja/choicelists.py,sha256=QHjWQWLnJCKSGnLIKeGqnCw41JYvcbTkCeXjBpWh23w,1466
3519
3519
  lino/modlib/jinja/loader.py,sha256=MX027X_UuQPqq0wOUr06QnOkdTzGpksNv0Om1CGp61I,1765
3520
- lino/modlib/jinja/mixins.py,sha256=X9ARAUsrD72sVj69cN3fl-auNnmz_0gVfG_-WXPHx_o,2837
3520
+ lino/modlib/jinja/mixins.py,sha256=YqvNt1JB7as6IohBoyP8DqHldxmg9yOfrN27V5cn7Ho,2913
3521
3521
  lino/modlib/jinja/models.py,sha256=vXNFS78qP-qIzeSmnXJNOpvW_IfH1h6hhPgYjfkvvD0,207
3522
3522
  lino/modlib/jinja/renderer.py,sha256=PtjhJdidlOOGzRdyUQlaEIj2FhFFuthFB80-bkSfsoM,6156
3523
3523
  lino/modlib/jinja/config/jinja/status.jinja.rst,sha256=XEsfXd82B0fLsBfds5-pXdeDWVr4ccTv7WyGa9ayigk,312
@@ -3534,7 +3534,7 @@ lino/modlib/languages/fixtures/iso-639-3_20100707.tab,sha256=u8PwI2s8shy0_Val5-s
3534
3534
  lino/modlib/linod/__init__.py,sha256=efmj_Kz3OO2zF1lvs7P459iufYGimH1-6Ge6Cbq85tQ,2665
3535
3535
  lino/modlib/linod/choicelists.py,sha256=Cu82s1QpcGFmKUXJsg-7TSqpaESBCZKOEfxzFlJP06I,2626
3536
3536
  lino/modlib/linod/consumers.py,sha256=XBjA1fflJ-e9yWRMKXyQAhrOklYzs5JRhEeGMOKWFqM,6730
3537
- lino/modlib/linod/mixins.py,sha256=gZci22_m3GNUNycLSvMelG1sUQLCCho2gniPTegopMs,13027
3537
+ lino/modlib/linod/mixins.py,sha256=Dff7rX5Ih0uJul0jID6tdY5CIZa6z6iq9feXIjNohkQ,11691
3538
3538
  lino/modlib/linod/models.py,sha256=yqGytDq7KxkHBCZpwF-7GQdR3nGh6-rBB-gLkvqoouQ,2361
3539
3539
  lino/modlib/linod/routing.py,sha256=3pYJT6FbaVMj6rKkDVv3E5VTFFc9nglIDeo6dgAKu8Q,2399
3540
3540
  lino/modlib/linod/utils.py,sha256=dE973Xib6Be1DvNsZ0M5wzY_jpkk35R21WKs-jQPorM,339
@@ -3542,7 +3542,7 @@ lino/modlib/linod/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
3542
3542
  lino/modlib/linod/fixtures/linod.py,sha256=qCFU2IktRVEdt1lChwu6kZLLnd4Ppfq_x9_2qGYelTY,1173
3543
3543
  lino/modlib/linod/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3544
3544
  lino/modlib/linod/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3545
- lino/modlib/linod/management/commands/linod.py,sha256=RsUgUBz-soF8wkfgUE8KJiLZltrhEeCm1m7AFNthUps,3826
3545
+ lino/modlib/linod/management/commands/linod.py,sha256=WjtRgcNxSHEZFClvQTQD8DAlVD_P5qK40ZG8xmsuQgM,3115
3546
3546
  lino/modlib/memo/__init__.py,sha256=GdzNURo3v_o5PxTBZ_KNkzrjlcDzn4AO95pYBbHmk8Q,5389
3547
3547
  lino/modlib/memo/mixins.py,sha256=wk1bwb28GTBwXI2QJSo-6kJLfpjFwXTf5S8McjmZ0Gw,11251
3548
3548
  lino/modlib/memo/models.py,sha256=zUEvWu0dK5QhkU3DeMqNqsrzSUzOl6DLZaJBNytfgrs,4388
@@ -3590,7 +3590,7 @@ lino/modlib/printing/config/report/Default.wk.html,sha256=4Ssx2LWm1gVpXf0Q4XoSY1
3590
3590
  lino/modlib/publisher/__init__.py,sha256=bGQduKmgzr_lU2N6H4q4I5ZmW-3iwdPuT2D8dpiVOiI,2644
3591
3591
  lino/modlib/publisher/choicelists.py,sha256=gVzjyp1sJ-XewAW-I_bCrKdTLgygLzh1ZwFI1rKyPdo,9070
3592
3592
  lino/modlib/publisher/mixins.py,sha256=yRxAtFSNe9aVvdY-th_a5wmQ76jBfKYWzeNUn-efJMA,6651
3593
- lino/modlib/publisher/models.py,sha256=3g_tPavQ6ZFQsHYkzbBe_LXPeq0TUgsU1JrR6bxuRT0,17149
3593
+ lino/modlib/publisher/models.py,sha256=Yn6MRp2_iHIXw-kwlfaQP4mfSkVPJdjEBJ8pqgltcPQ,17260
3594
3594
  lino/modlib/publisher/renderer.py,sha256=9RoyVMQcbwh_GcbDJO8BWhCKe1kpbDc9M45hrMqzpNs,1922
3595
3595
  lino/modlib/publisher/ui.py,sha256=qWp3JWhO6zN_HSZvSlolmNTgiZgoJeY2_TIDh9nYf3Q,4491
3596
3596
  lino/modlib/publisher/views.py,sha256=_0ktDagke4TOWVekJOnaRJBrbKqn27Yfz3SC2UnHKTM,2423
@@ -3598,11 +3598,11 @@ lino/modlib/publisher/config/publisher/base.html,sha256=nrWuN4lhP1O4ORdYTaY3fS2p
3598
3598
  lino/modlib/publisher/config/publisher/default_list.pub.html,sha256=3ePc4U3xZxE-pqtzlnB9CRQWu8OQrtfXKEjyuc8BBUQ,578
3599
3599
  lino/modlib/publisher/config/publisher/item.pub.html,sha256=973RYgCAYG1SYIB4bwfVZFVTUo0WD2I98PsoPgjQ8XA,154
3600
3600
  lino/modlib/publisher/config/publisher/login.html,sha256=_j_MmwVv_UohMnEDADo99XQmUqGvOWTlCDPW1oaSWpo,495
3601
- lino/modlib/publisher/config/publisher/page.pub.html,sha256=jnWP-uhhJZ0fjNHJ1Fo-0AIsiOFopzesOdyVtuSuM_w,2264
3601
+ lino/modlib/publisher/config/publisher/page.pub.html,sha256=kjUTHInDLu25QiOyZDpxumMBt2zjMLbDrMEiJhm6y5g,2568
3602
3602
  lino/modlib/publisher/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3603
3603
  lino/modlib/publisher/fixtures/demo2.py,sha256=KrF672tNLlhMX6EkxKDfcvvE9pGH4k9NgF7Rk5DkiLg,366
3604
3604
  lino/modlib/publisher/fixtures/std.py,sha256=7xpMOr4kd-4SX8uhwxagktUlcXAr6fdZfD-H519CR6g,987
3605
- lino/modlib/publisher/fixtures/synodalworld.py,sha256=aoHrQOu7qPWsPWbvEOfoVMdWQPFYBM85EUmJgcbS0tI,398
3605
+ lino/modlib/publisher/fixtures/synodalworld.py,sha256=7OPQXVq2Ou8fcRlNli0UFXoGhGPtC6AVXnGoCqn5oKc,767
3606
3606
  lino/modlib/restful/__init__.py,sha256=9CqWTQkJ2KpLW5MI1bKqKZU_7nXxodxl7HSgc8Agwmw,1506
3607
3607
  lino/modlib/restful/fields.py,sha256=8dik0q2LaCtJhQX1lu-fe-y_38xdhAWy1ZX3EKmJWfs,567
3608
3608
  lino/modlib/restful/serializers.py,sha256=AVyA19UkRUJ9AjlUN0fJyrj1LgfOyp-a2ANcVkecWtA,1143
@@ -4579,7 +4579,7 @@ lino/templates_old/404.html,sha256=9O5EJgULwLws0X1LjNig3xT4l9Hw04MBIGswD-EAlnw,2
4579
4579
  lino/templates_old/500.html,sha256=inOR744w4FGJM_fCSQcnlk-OIYQpaBTxUQWgXQozon4,496
4580
4580
  lino/templates_old/base.html,sha256=qYqj5-u1UPtpatrFkqBr3MnwS0oFUXCyQRcuNZczDfk,1641
4581
4581
  lino/templates_old/base_site.html,sha256=NcLEk0kBT1b-SrhoGpDPUej7NExqJ9-dP1kbrvwBzrs,255
4582
- lino/utils/__init__.py,sha256=r3PhUv44LoVlci5lARpqvq-8re6IwmfXxbTjDU-FVDs,18407
4582
+ lino/utils/__init__.py,sha256=6zmXTodRdV_hmdCcWsn_vBJUFlMZupc8O55vAOvWS9w,16838
4583
4583
  lino/utils/addressable.py,sha256=o7bmLbyvrmOoAT478s7XqjWKvnZ7zSXj4k7Xf0ccqf8,2213
4584
4584
  lino/utils/ajax.py,sha256=npCS0WumhTQlBzXxQPKnp2sYCRcPsYcbFqzE2ykVc4Q,3254
4585
4585
  lino/utils/choosers.py,sha256=9jjeLz-QcWVBfR8_GdY4PNYoqIYM63OI3OvOL2l1ZaU,17604
@@ -4625,6 +4625,7 @@ lino/utils/soup.py,sha256=tQNk9MweAWH9s14rCPQI0XNc4vuWrEc2leBGqnGrsCg,10937
4625
4625
  lino/utils/sql.py,sha256=kwCgv5E3n7uLjpXOEiaF2W2p8LeGXcP39xgtnqvzTP8,6795
4626
4626
  lino/utils/sqllog.py,sha256=kUCnBfFjExhItJ1w76WT9VWPaWWq8NQM-kSIMIIZnRU,3282
4627
4627
  lino/utils/ssin.py,sha256=Wo-9M0pG0uEYEGvmz8N--iRIqJe3ETtgLOXoung7qlE,4232
4628
+ lino/utils/sums.py,sha256=SATwDna9_B8_slLOBp47xO5fWtdgObhy66ux1eZf1u4,2136
4628
4629
  lino/utils/test.py,sha256=_aKfp3PdOq2KzR2uTev7joix9bs_cN22QprozmUzvUI,7680
4629
4630
  lino/utils/textfields.py,sha256=suTUK_zwTcxT1eHWAlif_AoRXxG_0AW6JhdbOv1zDSQ,2239
4630
4631
  lino/utils/ucsv.py,sha256=KjhGh7K_yxQOM0gHWjn4BjPdi_1aLPHkem5J8nNCqD4,2235
@@ -4634,8 +4635,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
4634
4635
  lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
4635
4636
  lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
4636
4637
  lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
4637
- lino-25.4.4.dist-info/METADATA,sha256=PnRkn-GRvLWsBi5pyAO6iggfwDKypr8ZWdiuhYqRN0Y,42534
4638
- lino-25.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4639
- lino-25.4.4.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4640
- lino-25.4.4.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4641
- lino-25.4.4.dist-info/RECORD,,
4638
+ lino-25.4.5.dist-info/METADATA,sha256=iRK3Rzw51gGXTd8xkApmMmpq6BLWOZZC3WV-3NFQLTw,42534
4639
+ lino-25.4.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4640
+ lino-25.4.5.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4641
+ lino-25.4.5.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4642
+ lino-25.4.5.dist-info/RECORD,,
File without changes