WhakerPy 2.2__py3-none-any.whl → 2.4__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.
whakerpy/__init__.py CHANGED
@@ -25,6 +25,6 @@ from .httpd import *
25
25
  from .messages import error
26
26
  from .webapp import *
27
27
 
28
- __version__ = "2.2"
28
+ __version__ = "2.4"
29
29
  ___author__ = "Brigitte Bigi"
30
30
  __copyright__ = "Copyright (C) 2023-2026 Brigitte Bigi, CNRS, Laboratoire Parole et Langage, Aix-en-Provence, France"
@@ -224,6 +224,33 @@ class TagNode(BaseTagNode):
224
224
  """Remove all children of the node."""
225
225
  self._children.clear()
226
226
 
227
+ # -----------------------------------------------------------------------
228
+ # Convenient method to create and add a child node
229
+ # -----------------------------------------------------------------------
230
+
231
+ def element(self, tag: str = "div", ident=None, class_name=None, value=None) -> HTMLNode:
232
+ """Create an HTML node and append it as a direct child of this node.
233
+
234
+ Convenient method to create a node and add it to the children of this
235
+ node in a single call, for example self.element("p", value="Hello.").
236
+
237
+ :param tag: (str) HTML element name
238
+ :param ident: (str) Optional identifier of the created node
239
+ :param class_name: (str) Optional value of the class attribute
240
+ :param value: (str) Optional text content of the tag
241
+ :return: (HTMLNode) The created and appended node
242
+
243
+ """
244
+ attributes = dict()
245
+ if ident is not None:
246
+ attributes["id"] = str(ident)
247
+ if class_name is not None:
248
+ attributes["class"] = str(class_name)
249
+
250
+ node = HTMLNode(self.identifier, ident, tag, attributes=attributes, value=value)
251
+ self.append_child(node)
252
+ return node
253
+
227
254
  # -----------------------------------------------------------------------
228
255
  # HTML management: getters and setters
229
256
  # -----------------------------------------------------------------------
@@ -284,7 +311,7 @@ class TagNode(BaseTagNode):
284
311
  html = ""
285
312
  try:
286
313
  # For some tags, the space char is meaningful. textarea is one of them. others???
287
- if self.tag != "textarea":
314
+ if self.tag not in ("textarea", "pre"):
288
315
  html += indent + " " * nbs
289
316
  html += self._value
290
317
  html += "\n"
whakerpy/httpd/handler.py CHANGED
@@ -218,6 +218,14 @@ class HTTPDHandler(http.server.BaseHTTPRequestHandler):
218
218
  elif mime_type == "text/html":
219
219
  events = HTTPDHandlerUtils.parse_query_string(query_string)
220
220
  content, status = self._bakery(handler_utils, events, mime_type)
221
+ # A GET is a navigation, not a form submission: query
222
+ # parameters the recipe does not handle (status 205) must
223
+ # not prevent the page from being served. The baked content
224
+ # is complete: serve it with a 200.
225
+ if status.code == 205:
226
+ logging.warning("Ignored unknown GET events {} for page {}"
227
+ "".format(events, self.path))
228
+ status = HTTPDStatus(200)
221
229
 
222
230
  # Unknown mime type: try to get a static file anyway.
223
231
  else:
whakerpy/httpd/hutils.py CHANGED
@@ -10,7 +10,7 @@
10
10
  ..
11
11
  -------------------------------------------------------------------------
12
12
 
13
- Copyright (C) 2023-2024 Brigitte Bigi, CNRS
13
+ Copyright (C) 2023-2026 Brigitte Bigi, CNRS
14
14
  Laboratoire Parole et Langage, Aix-en-Provence, France
15
15
 
16
16
  This program is free software: you can redistribute it and/or modify
@@ -49,6 +49,12 @@ from .hstatus import HTTPDStatus
49
49
  from .permissions import UnixPermissions
50
50
  from .permissions import FileAccessChecker
51
51
 
52
+ # The stdlib "mimetypes" default type for ".js" changed across Python
53
+ # versions (RFC 9239 moved it from "application/javascript" to
54
+ # "text/javascript"). Pin it explicitly so the mime type returned by
55
+ # get_mime_type() is identical on every supported Python version.
56
+ mimetypes.add_type("text/javascript", ".js")
57
+
52
58
  # -----------------------------------------------------------------------
53
59
 
54
60
 
@@ -203,21 +203,22 @@ class FileAccessChecker:
203
203
  def __check_permission_for_role(self, role: str) -> bool:
204
204
  """Helper function to check permissions for a single role.
205
205
 
206
+ The permission of a role is given by the mode bits of the file
207
+ only: the role does not have to match the effective uid/gid of
208
+ the current process. It answers "is the file readable by this
209
+ role", not "can the current process read it as this role" --
210
+ otherwise no file can be served from a mount owning the files
211
+ to a foreign user, like a FUSE/NTFS partition.
212
+
206
213
  :param role: (str) Who to check permissions for: 'others', 'group', or 'owner'.
207
214
 
208
215
  """
209
- current_uid = os.geteuid() # Effective user ID of the current process
210
- current_gid = os.getegid() # Effective group ID of the current process
211
-
212
- # Check owner, group, and others' permissions
213
216
  mode = self.__file_stat.st_mode
214
- owner_uid = self.__file_stat.st_uid
215
- group_gid = self.__file_stat.st_gid
216
217
 
217
218
  # Determine read permission based on the role
218
- if role == "owner" and current_uid == owner_uid:
219
+ if role == "owner":
219
220
  return bool(mode & stat.S_IRUSR) # Owner read permission
220
- elif role == "group" and current_gid == group_gid:
221
+ elif role == "group":
221
222
  return bool(mode & stat.S_IRGRP) # Group read permission
222
223
  elif role == "others":
223
224
  return bool(mode & stat.S_IROTH) # Others' read permission
@@ -8,7 +8,7 @@
8
8
  ..
9
9
  -------------------------------------------------------------------------
10
10
 
11
- Copyright (C) 2023-2025 Brigitte Bigi
11
+ Copyright (C) 2023-2026 Brigitte Bigi
12
12
  Laboratoire Parole et Langage, Aix-en-Provence, France
13
13
 
14
14
  This program is free software: you can redistribute it and/or modify
@@ -270,8 +270,8 @@ class WebSiteData:
270
270
  def __get_json_whakerpy_section(filename: str):
271
271
  """Return the configuration section related to WhakerPy.
272
272
 
273
- - Look for a top‐level "WhakerPy" key (new format).
274
- - Otherwise use the full JSON (old format) and issue a deprecation warning.
273
+ Requires a top-level "WhakerPy" key, introduced in WhakerPy 1.2.
274
+ Configuration files predating this key are not supported.
275
275
 
276
276
  :param filename: path to JSON configuration file
277
277
  :return: dict with keys "pagespath", "<page>.html", …
@@ -60,6 +60,7 @@ key/values:
60
60
 
61
61
  import os
62
62
  import types
63
+ import logging
63
64
 
64
65
  from ..httpd import HTTPDStatus
65
66
  from ..httpd import HTTPDHandlerUtils
@@ -238,6 +239,14 @@ class WSGIApplication(object):
238
239
  else:
239
240
  # Process dynamic content. Events are empty if POST request.
240
241
  events, accept = handler_utils.process_post(environ['wsgi.input'])
242
+
243
+ # A GET carries its data in the query string, the very same way a
244
+ # POST carries it in its body -- e.g. for a plain HTML form using
245
+ # method="get" (see do_GET() of the httpd request handler).
246
+ is_get = environ.get("REQUEST_METHOD", "GET").upper() == "GET"
247
+ if is_get is True:
248
+ events = HTTPDHandlerUtils.parse_query_string(environ.get("QUERY_STRING", ""))
249
+
241
250
  has_to_return_data = HTTPDHandlerUtils.has_to_return_data(accept)
242
251
  content, status = HTTPDHandlerUtils.bakery(
243
252
  self._pages,
@@ -246,6 +255,16 @@ class WSGIApplication(object):
246
255
  events,
247
256
  has_to_return_data
248
257
  )
258
+
259
+ # A GET is a navigation, not a form submission: query parameters
260
+ # the recipe does not handle (status 205) must not prevent the
261
+ # page from being served. The baked content is complete: serve
262
+ # it with a 200.
263
+ if is_get is True and status.code == 205:
264
+ logging.warning("Ignored unknown GET events {} for page {}"
265
+ "".format(events, page_name))
266
+ status = HTTPDStatus(200)
267
+
249
268
  if has_to_return_data is False:
250
269
  content = self.__policy.finalize_html(content)
251
270
  return content, status
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WhakerPy
3
- Version: 2.2
3
+ Version: 2.4
4
4
  Summary: Web HTML maker in Python - a light web application framework
5
5
  Author-email: Brigitte Bigi <contact@sppas.org>
6
6
  Project-URL: Repo, https://github.com/brigitte-bigi/WhakerPy
@@ -53,47 +53,86 @@ Dynamic: license-file
53
53
 
54
54
  ### Uses cases
55
55
 
56
- You want all users to get access to dynamic web pages via web browser. You then need to create a web application allowing creating HTML pages and communicating via HTTPD.
57
- WhakerPy is a Python library of such a tool.
56
+ You want your users to reach dynamic web pages from a browser, without adopting a full templating stack. WhakerPy is a Python library to build and serve that content.
58
57
 
59
- In fact, you may have already heard of Django — high-level, full-stack framework, and Flash — a micro-framework. Both of them generate output in the form of content from the model presented and formatted based on a template file.
60
- The WhakerPy library does not offer views, templates or models!
61
- On the contrary, **WhakerPy offers a 100% pure-Python solution** by creating HTML pages entirely dynamically, or with a static content.
58
+ Django and Flask both render a template file against a model. WhakerPy has no views, no templates and no models: a page is an HTML tree, built and serialized directly in Python.
62
59
 
63
60
  WhakerPy is your solution if:
64
- - you're looking to design a relatively simple web app with a few static pages;
65
- - and/or you want a full control on dynamic HTML creation content;
66
- - **you want to build a web app with nothing but Python**.
61
+
62
+ - you want a relatively simple web app with a few static or semi-dynamic pages;
63
+ - you want full, programmatic control over the generated HTML, without a template language to learn;
64
+ - **you want to build and secure a web app with nothing but Python and its standard library**.
67
65
 
68
66
 
69
67
  ### Features
70
68
 
71
- WhakerPy is a Python, free, open source, self-hosted library to create dynamic HTML content and web applications. It offers more flexibility than a framework: it's a library!
72
- WhakerPy is then a collection of packages and modules that help developers to create static or dynamic web content without having to worry about the details involved.
69
+ WhakerPy is a free, open-source, self-hosted Python library — not a framework — to build dynamic HTML content and web applications. It stays out of the way: no views, no templates, no models, no third-party dependency for the core library.
73
70
 
74
- * Build powerful web apps with all the flexibility of Python: building a web app with WhakerPy is as simple as coding with Python
75
- * Create HTML pages dynamically: create a tree with nodes and serialize the tree into a string
76
- * Can save as static HTML files
77
- * Create a web-frontend with its HTTPD response "bakery" system
78
- * Run locally with its HTTPD server
79
- * Run locally or remotely with its WSGI application
71
+ * Build an HTML page as a tree of Python objects (`HTMLTree`, `HTMLNode`), then serialize it to a string or to a static file
72
+ * Serve pages dynamically with a "bakery" response system that re-renders only what changed
73
+ * Deploy the same application three ways, unchanged: as static files, behind the built-in HTTPD dev server, or behind a production WSGI server (Apache, nginx, gunicorn, ...)
74
+ * Parse GET query strings, POST bodies (JSON, url-encoded forms) and multipart file uploads yourself — hand-written against the standard library, no `python-multipart`, and no dependency on the `cgi` module removed from Python 3.13
75
+ * Protect pages and endpoints with a built-in HTTP security policy, shared by the HTTPD server and the WSGI application:
76
+ - a configurable blacklist rejecting requests by URL path or User-Agent
77
+ - HMAC-SHA256 signed, time-limited URLs (tokens) to share ephemeral links without sessions, cookies or server-side storage — no `itsdangerous`, no `PyJWT`
78
+ * Serve static files only after checking Unix read permissions (owner/group/others), so a misconfigured file is never served by accident
80
79
 
81
80
 
82
81
  ### Main advantages
83
82
 
84
- >Creating and manipulating HTML from the power of Python!
83
+ >Build, secure and serve dynamic HTML web applications with nothing but the power of Python!
84
+
85
+ * easy to learn: one consistent, explicit syntax, from the HTML tag tree to the HTTP response
86
+ * a single dependency-free package: HTML generation, HTTPD/WSGI serving, security policy and upload parsing all ship in the Python Standard Library only
87
+ * open-source and easily customizable: pure-Python, Object-Oriented, no compiled extension
88
+ * portable: runs anywhere Python runs, from a local script to a production WSGI deployment
89
+
90
+
91
+ ## How it works
92
+
93
+ WhakerPy is not Flask, not Django, and does not try to be. There is no template engine and nothing to render:
94
+ a page **is** a tree of Python objects.
95
+
96
+ ### No templates — an HTML tree, in Python
97
+
98
+ Flask and Django keep a page as a text file (Jinja2 / Django templates) with placeholders (`{% if %}`, `{% for %}`, `{{ variable }}`) filled from a context dictionary at render time.
99
+ That file is not Python: it has its own syntax, its own scoping rules, and its own way of drifting out of sync with the code that feeds it.
100
+
101
+ WhakerPy has no such file. An `HTMLTree` is a tree of `HTMLNode` objects — `<head>`, `<body>` and every element inside are Python instances, built and traversed with ordinary Python: `if`, `for`, functions, classes.
102
+ To change what gets rendered, write Python; there is no second language to learn:
103
+
104
+ ```python
105
+ >>> if user.is_admin:
106
+ ... htree.body_main.append_child(HTMLNode(main_id, None, "p", value="Admin panel"))
107
+ ```
108
+
109
+ That is the whole of "the WhakerPy templating language".
85
110
 
86
- * easy to learn, consistent, simple syntax
87
- * flexible and easy usage
88
- * open-source: easily add new features and functionalities
89
- * easily customizable: it's a pure python library in Object-Oriented Programming
90
- * portable: can be hosted on any web server - as soon as python is available, or used locally
91
- * it is distributed as a single folder module and has no dependencies other than the Python Standard Library.
111
+ ### One page class, three ways to serve it
112
+
113
+ A page is a subclass of `BaseResponseRecipe`. `create()` builds the parts of the tree that never change; `bake(events)` runs on every request, decides from the incoming `events` (query string or POST data) whether anything changed, and only rebuilds the dynamic part when it did — a static page is never re-serialized for nothing.
114
+
115
+ The same recipe instances can then be exposed three ways, without touching the page code:
116
+
117
+ - `tree.serialize_to_file(...)` — dumped once to a static `.html` file;
118
+ - `BaseHTTPDServer` / `HTTPDHandler` — the stdlib `http.server`, for local development or a small deployment with zero extra process;
119
+ - `WSGIApplication` — the same recipes, the same bakery, behind any production WSGI server (Apache, nginx + gunicorn/uWSGI, ...).
120
+
121
+ ### Security is a policy, not a decorator
122
+
123
+ `HTTPDPolicy` sits in front of both servers and is configured once: a blacklist of paths/User-Agents, and a set of HMAC-signed, time-limited URLs.
124
+ It applies identically whether a request comes through the dev HTTPD server or through WSGI in production — one place to configure it, not one per route.
125
+
126
+ ### Minimalism as a security stance
127
+
128
+ WhakerPy is deliberately small: code that is not written cannot be exploited. The handlers only emit the status codes their own logic actually needs — 200 (OK), 205 (Reset Content, an unhandled GET is still served), 403 (Forbidden), 404 (Not Found, including an expired signed URL), 410 (Gone, shuts the server down), 418 (I'm a Teapot, a blacklisted request), 500 (Internal Server Error) — which covers the large majority of real needs, instead of a generic status-code framework bolted onto every route.
129
+
130
+ `HTTPDStatus` still validates against the full IANA status registry, so any `BaseResponseRecipe` is free to return whichever code its page actually needs. If the built-in handlers should react to more of them, that is what contributions are for.
92
131
 
93
132
 
94
133
  ## Get and install WhakerPy
95
134
 
96
- Get it from its repository <https://sourceforge.net/projects/whakerpy/> or from Pypi <https://pypi.org/project/whakerpy/>, and get documentation <https://whakerpy.sourceforge.io>.
135
+ Get it from its repository <https://github.com/brigitte-bigi/WhakerPy> or from Pypi <https://pypi.org/project/whakerpy/>, and get documentation <https://whakerpy.sourceforge.io>.
97
136
 
98
137
  ### Install from pypi.org:
99
138
 
@@ -119,8 +158,8 @@ Download the latest ".zip" from it's web page and unpack it, or clone the reposi
119
158
  4. "sample": a web application sample
120
159
 
121
160
  ```bash
122
- > unzip WhakerPy-1.1.zip
123
- > git clone https://git.code.sf.net/p/whakerpy/code whakerpy-code
161
+ > unzip WhakerPy-2.2.zip
162
+ > git clone https://github.com/brigitte-bigi/WhakerPy.git whakerpy-code
124
163
  > python -m pip install .
125
164
  ```
126
165
 
@@ -197,23 +236,15 @@ created from a custom `BaseResponseRecipe()` object, available in the file
197
236
  `sample/response.py`. The response is the interface between a local back-end
198
237
  python application and the web front-end.
199
238
 
200
- For a more complex example of an already in-used application web frontend, see:
201
- <https://sourceforge.net/p/sppas/code/ci/master/tree/sppas/ui/swapp/app_setup/setupmaker.py>.
202
-
203
239
 
204
240
  ## Projects using WhakerPy
205
241
 
206
- WhakerPy was initially developed within SPPAS <https://sppas.org>.
207
- It was extracted from its original software by the author to lead its own life as standalone package.
208
- The "setup" of SPPAS is entirely based on `whakerpy` API.
209
-
210
- Other projects:
211
-
212
242
  - the website <https://auto-cuedspeech.org>
213
243
  - the website <https://sppas.org>
214
- - the web appis of SPPAS
244
+ - the SPPAS graphical user interface
215
245
  - the intranet of the Laboratoire Parole et Langage
216
- - *contact the author if your project is based on WhakerPy*
246
+ - *contact the author if your project is based on WhakerPy* and you want it to be included in this list.
247
+
217
248
 
218
249
 
219
250
  # The developer's corner
@@ -313,4 +344,4 @@ or products, in accordance with the best practices of the AGPL license.
313
344
  Use the following reference to cite WhakerPy:
314
345
 
315
346
  > Brigitte Bigi. WhakerPy, a Python library to create dynamic HTML content and
316
- > web applications. Version 1.x. 202x. <https://hal.science/hal-04743687>
347
+ > web applications. Version 2.2. 2026. <https://hal.science/hal-04743687>
@@ -1,6 +1,6 @@
1
1
  sample/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sample/response.py,sha256=_jAaWLrHuGBYy0Y0aywxGrSDX365MSfASqWTISIotqw,12532
3
- whakerpy/__init__.py,sha256=DA3YMbr2kjiqkDY-v5PFzQu-OWWw3PAUtzgSkdXVJDQ,1827
3
+ whakerpy/__init__.py,sha256=5YZutVN8zQxpO9NNL-lL2TGUkaBv3dH70yZpOWoVz80,1827
4
4
  whakerpy/messages.py,sha256=NSnQYFfarU16T59Zryuy2g2x7UwA8xgY_UDwRLQqPm4,2385
5
5
  whakerpy/htmlmaker/__init__.py,sha256=zCHYhW37cyK68u90w2tFBLJx59ApGvqgNkxiIzuVtTM,3333
6
6
  whakerpy/htmlmaker/hconsts.py,sha256=6I0Mmp72ZBzePGw-nH4qjD7TvENxXUPAT-qTeljwGe8,12115
@@ -16,25 +16,25 @@ whakerpy/htmlmaker/emptynodes/emptyelts.py,sha256=wqCtFMZU_bTCP4Z63Q3Pgwo1pNC1tM
16
16
  whakerpy/htmlmaker/emptynodes/emptynode.py,sha256=caUy9V7CG-G_lgVwBAjMNKEdVeI8FBkTT28aREuuyXA,9920
17
17
  whakerpy/htmlmaker/htmnodes/__init__.py,sha256=hOrs9nhL2aMCvukuPoOxRdinPAK_QFpWBpmTnp-iTQ8,1547
18
18
  whakerpy/htmlmaker/htmnodes/htmelts.py,sha256=CTLX6hQWpulJuwW5F75gLTbKiiERkAkRLZ0LiHz9_lE,5398
19
- whakerpy/htmlmaker/htmnodes/htmnode.py,sha256=gM3c-9dj9kBO_8JGHOqca1ZDIOWdEgNy1_BIwcUtDqI,11603
19
+ whakerpy/htmlmaker/htmnodes/htmnode.py,sha256=XpFspvlC5uFOPa723txR72OOZRPTGUFWVIPm0QjFIvw,12827
20
20
  whakerpy/httpd/__init__.py,sha256=A4_Y6oN-8PY3TmGssX6DGKlUsE0wAGsNc5N1oo8HDK8,2031
21
- whakerpy/httpd/handler.py,sha256=EecczKlv8HMTWthJgXJLRLGlgTELnYnUmoTa1LkGR14,11007
21
+ whakerpy/httpd/handler.py,sha256=7hZc2rMALqRK2qKI1hYeN6FrrhBNDVcil5hjNdCpO4I,11518
22
22
  whakerpy/httpd/hblacklist.py,sha256=H9-hhLGx9StYGo7ThBs23dVuqNRTveGvkt_SftH_oIc,6605
23
23
  whakerpy/httpd/hpolicy.py,sha256=LZBY3eSz9xrpPUlV-ymQxdx34LN33acX9SBsqaNVrm0,9827
24
24
  whakerpy/httpd/hresponse.py,sha256=bjv-PS0xQ3RR7z9XvzOptxWkSdmlutRuFWqgdUqmDVI,9412
25
25
  whakerpy/httpd/hserver.py,sha256=1X8lGKIZNBFyoNHcLHtQPkOkEORvZ19HLGahOeE8uiY,6302
26
26
  whakerpy/httpd/hsignedurl.py,sha256=ZLGENB3-Y1jOANaMP0E71s3xqFmdbBb8hgJf7IODG1g,13295
27
27
  whakerpy/httpd/hstatus.py,sha256=rrWDvPXTyAh8l4IJY0xBDkZxeKC7t3_5-XlVHtUDSo8,7998
28
- whakerpy/httpd/hutils.py,sha256=C7gkMka-dEpK_Bt6RhCSDdrSjZO1sgZkuAMIWTPSZSk,28631
29
- whakerpy/httpd/permissions.py,sha256=PNl2XQwYtpJVzXaPgWVT-2TqnN8jsr-b6ZhZasFbono,8463
28
+ whakerpy/httpd/hutils.py,sha256=OaEpCm54PfY8jNQePszKFUoSh8Kr_3iUkAWIEZ687hc,28951
29
+ whakerpy/httpd/permissions.py,sha256=KUpLR_qB_mjCzrg3xcCIgErSxD9OJQl72HTaqiOJeyw,8511
30
30
  whakerpy/webapp/__init__.py,sha256=FCeq1HYEqFhGyquWajWVhWJbAsOYihBedtdUwpnQvis,276
31
- whakerpy/webapp/webconfig.py,sha256=8JAWncQip-uAeLQaIWzcmZIczRWlaKRbR5ZJAdEPoWs,11222
31
+ whakerpy/webapp/webconfig.py,sha256=LmGUrfZ3btFSY3DoMpme2UXgiyHl48VlpnSXUyMk3MM,11215
32
32
  whakerpy/webapp/weblocalserver.py,sha256=XjtVkUQPeFsVvX4ARLc_2_9eqc2KBmuQHMlxbr7DMUE,3180
33
33
  whakerpy/webapp/webresponse.py,sha256=q4p12QHRFmq52K0lLkdqPJfexkG2SqZLluYF7Zg406g,4430
34
- whakerpy/webapp/webwsgi.py,sha256=wmdN5MemNqFqrOhQVlpvp0-XSCGGdQ3SF8H4_qJMNG4,11554
35
- whakerpy-2.2.dist-info/licenses/AUTHORS.md,sha256=VrCusdQ56bvNDiq7w_MA5mZoZXuP_JOG-ybTwpRT_u4,805
36
- whakerpy-2.2.dist-info/licenses/LICENSE.md,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
37
- whakerpy-2.2.dist-info/METADATA,sha256=9UQr1f52WX1jijHmnoYZ9HNca3CVU9OPA74smalza_I,12055
38
- whakerpy-2.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
39
- whakerpy-2.2.dist-info/top_level.txt,sha256=ZqGTnE-rI2S_jcI0kxnNeKd0z25TBv8Vkbe2iJ74-E0,16
40
- whakerpy-2.2.dist-info/RECORD,,
34
+ whakerpy/webapp/webwsgi.py,sha256=6gmF9JxGn6pu3jYrJGGEnQG1msM3oi9q3y3sKQJmk2U,12511
35
+ whakerpy-2.4.dist-info/licenses/AUTHORS.md,sha256=VrCusdQ56bvNDiq7w_MA5mZoZXuP_JOG-ybTwpRT_u4,805
36
+ whakerpy-2.4.dist-info/licenses/LICENSE.md,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
37
+ whakerpy-2.4.dist-info/METADATA,sha256=oEM8cZQGhSD4gWNLBCvvwmWMGcc31Kf0bekFYVH20q4,15095
38
+ whakerpy-2.4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
39
+ whakerpy-2.4.dist-info/top_level.txt,sha256=ZqGTnE-rI2S_jcI0kxnNeKd0z25TBv8Vkbe2iJ74-E0,16
40
+ whakerpy-2.4.dist-info/RECORD,,
File without changes