clear-skies 2.0.2__py3-none-any.whl → 2.0.3__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 clear-skies might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: clear-skies
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: A framework for building backends in the cloud
5
5
  License: MIT
6
6
  Author: Conor Mancone
@@ -55,7 +55,7 @@ clearskies/backends/secrets_backend.py,sha256=r1k7YfZ7AKMsN2wWwx939QKeYWgh6PVkdi
55
55
  clearskies/column.py,sha256=_VkvfD_6d4brTkChUbevJe9e7sla8ovsiJ5cH1evoH4,52070
56
56
  clearskies/columns/__init__.py,sha256=a__-1hv-aMV3RU0Sd-BEkfItSJaG51EF2VeRQoTdka8,1990
57
57
  clearskies/columns/audit.py,sha256=0BBpBFsxz6JBWDPoyCb73EDJnN95pj1TXtTZqbT1qKU,7628
58
- clearskies/columns/belongs_to_id.py,sha256=VtI_LjaBRMNGoeZlF4WvTCgWXNdKP06c0ca5BrnnLbs,17792
58
+ clearskies/columns/belongs_to_id.py,sha256=10yImEA5xgA547kPIfRajcNwlCfXF9QTvai0-fBFxG0,17828
59
59
  clearskies/columns/belongs_to_model.py,sha256=b0eBBP6EoDEVOKP4ZZpdrD4BOadblTHzuH-KLWjOIrA,5807
60
60
  clearskies/columns/belongs_to_self.py,sha256=TbcXmRp6_OAycElrnOqaIZK5aDDheTsRoGc1M8pbAm0,3762
61
61
  clearskies/columns/boolean.py,sha256=CiYgMWvNdom8KDQk2Jt_kwWDtrLIyymrmLrP9zTYFTE,3938
@@ -138,10 +138,10 @@ clearskies/configs/writeable_model_column.py,sha256=ZwroRcvmNznggtNMSZiAmKAvllcV
138
138
  clearskies/configs/writeable_model_columns.py,sha256=D8E4aQRg1MX8-TM8lkyrWE-G_YoX2eerX4KLszgk0hA,323
139
139
  clearskies/configurable.py,sha256=FFkKwlQk17KOLnZiTsTF3hrCu2WxRSuuddNNhz9aySY,3049
140
140
  clearskies/contexts/__init__.py,sha256=f7XVUq2UKlDH6fjmcUWk6lbe9p_OaGpZ5ZjM6CuwTGQ,247
141
- clearskies/contexts/cli.py,sha256=HSzHieVsD3a0uQb6FBue17HVT4PjSY8LWhjW3rw7lQw,285
142
- clearskies/contexts/context.py,sha256=jbd8JdDkFruHiAYHZWrLjOvqz31LkvfApArCOVwkyfo,3630
143
- clearskies/contexts/wsgi.py,sha256=lENadrI53LYUCa3Mn4JjxhTn-53Ry9l6w2kBU3CQ5AM,611
144
- clearskies/contexts/wsgi_ref.py,sha256=Tkq0QYE7_ZdYvEcRKbyh-6UwwuExUadKdylwdxcCmQQ,1814
141
+ clearskies/contexts/cli.py,sha256=Hzh8vgkLoCn2Zx8AQIoLen4fMDPIvZqtSLfEcg_CPKc,2639
142
+ clearskies/contexts/context.py,sha256=S1iqSfV2gPnXz20-8AJU9QWh6IKa56jEV11g99iZO-o,3663
143
+ clearskies/contexts/wsgi.py,sha256=rJCfBZhyvPyRduyY9-ctvoQMcQXwRqRGwldidiUQWBY,3111
144
+ clearskies/contexts/wsgi_ref.py,sha256=BnV2hhusu5Hkd1cFLGXEn7COWS23u1NH9BDgAWlgX_M,2747
145
145
  clearskies/decorators.py,sha256=I91l4Iwe2swzkZPWUWywmIV2LfTBlw9IWmUfspKm3yY,1040
146
146
  clearskies/di/__init__.py,sha256=uJ1NkACEIaot7ALr3M6vQ55Pe9By_s_1z52ssAnw28E,466
147
147
  clearskies/di/additional_config.py,sha256=65INxw8aqTZQsyaKPj-aQmd6FBe4_4DwibXGgWYBy14,5139
@@ -164,7 +164,7 @@ clearskies/di/test_module/__init__.py,sha256=7YHQF7JHP0FdI7GdEGANSZ_t1EISQYhUNm1
164
164
  clearskies/di/test_module/another_module/__init__.py,sha256=8SRmHPDepLKGWTUSc1ucDF6U8mJPsNDsBDmBQCpzPWo,35
165
165
  clearskies/di/test_module/module_class.py,sha256=I_-wnMuHfbsvti-7d2Z4bXnr6deo__uvww9nds9qrlE,46
166
166
  clearskies/end.py,sha256=Z4C9n7OvBky_640oJ8d19jIr_ap1iqjGJqqO6s_zF5g,8919
167
- clearskies/endpoint.py,sha256=G3oFdAYOFeYVYpMwxib6mMO5FpuF2DtrTBW04spJ-aw,48827
167
+ clearskies/endpoint.py,sha256=DrsGSVh4EJZZ-scheC0vdwD5q5M51W1AIAUfD2J691E,48831
168
168
  clearskies/endpoint_group.py,sha256=OiGCxVyhLMORswawtYey9xLtCt4qjK8hGb9tIfPi5sc,11285
169
169
  clearskies/endpoints/__init__.py,sha256=CZJQ3rHF1GA0QvH9CKxUFM1pEnJrg-2wAj5rzIDdzjc,689
170
170
  clearskies/endpoints/advanced_search.py,sha256=c1fj81nohHGqe56t00o6kLN3XIh3kfy-aKy_C1pxwgw,21835
@@ -191,7 +191,7 @@ clearskies/functional/routing.py,sha256=tfIvP_Y29GTGr91_1ec3LSQFoTRwpkqU4BYHXPnB
191
191
  clearskies/functional/string.py,sha256=ZnkOjx8nxqZq2TV0CIb-Kz4onGoyekTX_WkLJM6XTmM,3311
192
192
  clearskies/functional/validations.py,sha256=cPYOTwWomlQrPvqPP_Jdlds7zZ5H9GABCP5pnGzC9T4,2821
193
193
  clearskies/input_outputs/__init__.py,sha256=9qeKJULw3MQ3zqkgBZice5d7qqRgsP3y-wkhWO2Y9vM,362
194
- clearskies/input_outputs/cli.py,sha256=3kSE7p9OitUFyajuALwOtggJCIMIl5p_gxIby6lXE1E,6005
194
+ clearskies/input_outputs/cli.py,sha256=_r9TNj0Z_8gDw0U_NJECuyLE89CxrHxVpXG6wT_Tt0w,6025
195
195
  clearskies/input_outputs/exceptions/__init__.py,sha256=3KiM3KaMYEKoToqCCQ4_no2n0W5ROqeBC0sI2Ix4P6w,82
196
196
  clearskies/input_outputs/exceptions/cli_input_error.py,sha256=kOFU8aLTLmeTL_AKDshxMu8_ufildg6p8ndhE1xHfb0,41
197
197
  clearskies/input_outputs/exceptions/cli_not_found.py,sha256=JBBuZA9ZwdkPhd3a0qaGgEPQrxh1fehy4R3ZaV2gWXU,39
@@ -200,7 +200,7 @@ clearskies/input_outputs/input_output.py,sha256=W5h8a9XFEqwi3h6v17okblz8sOozUj-m
200
200
  clearskies/input_outputs/programmatic.py,sha256=gfsNFIfiF5cvxi1aS9SgArcKYUgkL5gyxjCMQLwgivY,1711
201
201
  clearskies/input_outputs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
202
202
  clearskies/input_outputs/wsgi.py,sha256=94tgWxiwpwvJA9G4nG-hCHteg0pq9pIDCVhnheZuAQc,2544
203
- clearskies/model.py,sha256=MMeoMvwH_Ijca0hLxEf0f8UTczzliu1Lj91CxYOHogg,73385
203
+ clearskies/model.py,sha256=6ZJoWNKzjsQFvgXEaXybrO0HaaknoSkKYNYO_i0Ep0k,77189
204
204
  clearskies/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
205
  clearskies/query/__init__.py,sha256=ISF80_cG3rd574sRTdKKPxAdlSjtQh_ClXKKs_MSSSo,277
206
206
  clearskies/query/condition.py,sha256=9RJrYya1Kfzx0L_BMDKQhYkN9dB7NAq5kRK5mz51YdY,8624
@@ -243,7 +243,7 @@ clearskies/validators/minimum_value.py,sha256=Vz0e_9U5KYqBGqfrVBOtPiVtmNPWwFEGPA
243
243
  clearskies/validators/required.py,sha256=f8VE9sbvIorTIC0q_77DyDfDjAJYFJs-uTgJkAwLxX4,1362
244
244
  clearskies/validators/timedelta.py,sha256=nFEHefllfMZsguJoSdeWzaBsxBDZfMZoIdt5jZxeg0g,1932
245
245
  clearskies/validators/unique.py,sha256=DZA7dbLucYDShtr1bVP2pnmbzciIEMvYSrTjOKCQ4vM,1029
246
- clear_skies-2.0.2.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
247
- clear_skies-2.0.2.dist-info/METADATA,sha256=jsf2tuLi8kFkZmJENAnP9AIfNC-k_7spE1xaXXaLw0M,1762
248
- clear_skies-2.0.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
249
- clear_skies-2.0.2.dist-info/RECORD,,
246
+ clear_skies-2.0.3.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
247
+ clear_skies-2.0.3.dist-info/METADATA,sha256=xGVzdYA9RfscDKEcKZE8dwXCv6hbomOi67x1FH0864A,1762
248
+ clear_skies-2.0.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
249
+ clear_skies-2.0.3.dist-info/RECORD,,
@@ -356,11 +356,11 @@ class BelongsToId(String):
356
356
  parent_model = self.parent_model
357
357
  matching_parents = parent_model.where(f"{parent_model.id_column_name}={value}")
358
358
  matching_parents = self.apply_wheres(matching_parents)
359
- matching_parents = matching_parents.where_for_request(
359
+ matching_parents = matching_parents.where_for_request_all(
360
360
  matching_parents,
361
- self.input_output.routing_data,
362
- self.input_output.authorization_data,
363
361
  self.input_output,
362
+ routing_data=self.input_output.routing_data,
363
+ authorization_data=self.input_output.authorization_data,
364
364
  )
365
365
  if not len(matching_parents):
366
366
  return f"Invalid selection for {self.name}: record does not exist"
@@ -5,6 +5,112 @@ from clearskies.input_outputs import Cli as CliInputOutput
5
5
  class Cli(Context):
6
6
  """
7
7
  Run an application via a CLI command
8
+
9
+ This context converts a clearskies application into a CLI command. Here's a simple example:
10
+
11
+ ```
12
+ #!/usr/bin/env python
13
+ import clearskies
14
+
15
+ def my_function():
16
+ return "Hello World!"
17
+
18
+ cli = clearskies.contexts.Cli(my_function)
19
+ cli()
20
+ ```
21
+
22
+ Which you can then run as expected:
23
+
24
+ ```
25
+ $ ./example.py
26
+ Hello World!
27
+ ```
28
+
29
+ Routing is still supported, with routes and route parameters becoming CLI args:
30
+
31
+ ```
32
+ #!/usr/bin/env python
33
+ import clearskies
34
+
35
+ def my_function(name):
36
+ return f"Hello {name}!"
37
+
38
+ cli = clearskies.contexts.Cli(
39
+ clearskies.endpoints.Callable(
40
+ my_function,
41
+ url="/hello/:name",
42
+ return_standard_response=False,
43
+ )
44
+ )
45
+ cli()
46
+ ```
47
+
48
+ With a url of `/hello/:name` you would invoke like so:
49
+
50
+ ```
51
+ ./example.py hello Bob
52
+ Hello Bob!
53
+ ```
54
+
55
+ If the endpoint expects a request method you can provide it by setting the `-X` or `--request_method=`
56
+ kwargs. So for tihs example:
57
+
58
+ ```
59
+ #!/usr/bin/env python
60
+ import clearskies
61
+
62
+ def my_function(name):
63
+ return f"Hello {name}!"
64
+
65
+ cli = clearskies.contexts.Cli(
66
+ clearskies.endpoints.Callable(
67
+ my_function,
68
+ url="/hello/:name",
69
+ request_methods=["POST"],
70
+ )
71
+ )
72
+ cli()
73
+ ```
74
+
75
+ And then calling it successfully:
76
+
77
+ ```
78
+ ./example.py hello Bob --request_method=POST
79
+
80
+ ./example.py hello Bob -X POST
81
+ ```
82
+
83
+ You can pass data as a json string with the -d flag or set individual named arguments. The following
84
+ example just reflects the request data back to the client:
85
+
86
+ ```
87
+ #!/usr/bin/env python
88
+ import clearskies
89
+
90
+ def my_function(request_data):
91
+ return request_data
92
+
93
+ cli = clearskies.contexts.Cli(
94
+ clearskies.endpoints.Callable(
95
+ my_function,
96
+ )
97
+ )
98
+ cli()
99
+ ```
100
+
101
+ And these three calls are identical:
102
+
103
+ ```
104
+ ./example.py -d '{"hello": "world"}'
105
+
106
+ echo '{"hello": "world"}' | ./test.py
107
+
108
+ ./test.py --hello=world
109
+ ```
110
+
111
+ Although note that the first two are going to be preferred over the third, simply because with the
112
+ third there's simply no way to specify the type of a variable. As a result, you may run into issues
113
+ with strict type checking on endpoints.
8
114
  """
9
115
 
10
116
  def __call__(self): # type: ignore
@@ -48,6 +48,7 @@ class Context:
48
48
  bindings=bindings,
49
49
  additional_configs=additional_configs,
50
50
  class_overrides=class_overrides,
51
+ overrides=overrides,
51
52
  now=now,
52
53
  utcnow=utcnow,
53
54
  )
@@ -14,6 +14,62 @@ from clearskies.input_outputs import Wsgi as WsgiInputOutput
14
14
  class Wsgi(Context):
15
15
  """
16
16
  Connect your application to a WSGI server.
17
+
18
+ The Wsgi context is used to connect a clearskies application to a WSGI server of your choice. As with all
19
+ contexts, you first create it and pass in the application (a callable, endpoint, or endpoint group) as well
20
+ as any dependency injection parameters. Then, you call the context from inside of the function invoked by
21
+ your WSGI server, passing along the `environment` and `start_response` variables, and returning the response
22
+ from the context. Here's a simple example:
23
+
24
+ ```
25
+ import clearskies
26
+
27
+ def hello_world():
28
+ return "Hello World!"
29
+
30
+ wsgi = clearskies.contexts.Wsgi(hello_world)
31
+
32
+ def application(environment, start_response):
33
+ return wsgi(environment, start_response)
34
+ ```
35
+
36
+ You would then launch your WSGI server. For instance, here's how to launch it with uwsgi, which automatically
37
+ looks for a function called `application` and treats that as the WSGI starting point:
38
+
39
+ ```
40
+ uwsgi --http :9090 --wsgi-file test.py
41
+ ```
42
+
43
+ You could then:
44
+
45
+ ```
46
+ curl 'http://localhost:9090'
47
+ ```
48
+
49
+ And see the response from this "hello world" app. Note than in the above example I create the context outside
50
+ of the application function. Of course, you can do the opposite:
51
+
52
+ ```
53
+ import clearskies
54
+
55
+ def hello_world():
56
+ return "Hello World!"
57
+
58
+ def application(environment, start_response):
59
+ wsgi = clearskies.contexts.Wsgi(hello_world)
60
+ return wsgi(environment, start_response)
61
+ ```
62
+
63
+ The difference is that most wsgi servers will cache any objects created outside of the handler function (e.g. `application`
64
+ in this case). When you first create the context clearskies will configure and validate any endpoints attached.
65
+ Also, it will create an instance of the dependency injection container and cache it. If the context object is created
66
+ outside of the handler, and the server caches objects in this csae, then this validation will only happen once and
67
+ the DI cache will store objects in between HTTP calls. If you create your context inside the handler function, then
68
+ you'll end up with an empty cache everytime and you'll have slower responses because of clearskies checking the
69
+ application configuration everytime. Note that the DI system for clearskies grants you full cache control, so
70
+ by and large it's normal and expected that you'll persist the cache between requests by creating the context outside
71
+ of any handler functions.
72
+
17
73
  """
18
74
 
19
75
  def __call__(self, env, start_response): # type: ignore
@@ -14,6 +14,35 @@ from clearskies.input_outputs import Wsgi as WsgiInputOutput
14
14
  class WsgiRef(Context):
15
15
  """
16
16
  Use a built in WSGI server (for development purposes only).
17
+
18
+ This context will launch a built-in HTTP server for you, so you can run applications locally
19
+ without having to install extra dependencies. Note that this server is not intended for production
20
+ usage, so this is best used for simple tests/demonstration purposes. Unlike the WSGI context, where
21
+ you define the application handler and invoke the context from inside of it (passing along the
22
+ environment and start_response variables), in this case you simply directly invoke the context to
23
+ launch the server. The default port is 8080:
24
+
25
+ ```
26
+ #!/usr/bin/env python
27
+ import clearskies
28
+
29
+ def hello_world(name):
30
+ return f"Hello {name}!"
31
+
32
+ wsgi = clearskies.contexts.WsgiRef(
33
+ clearskies.endpoints.Callable(
34
+ hello_world,
35
+ url="/hello/:name",
36
+ )
37
+ )
38
+ wsgi()
39
+ ```
40
+
41
+ And to invoke it:
42
+
43
+ ```
44
+ curl 'http://localhost:8080/hello/Friend'
45
+ ```
17
46
  """
18
47
 
19
48
  port: int = 8080
clearskies/endpoint.py CHANGED
@@ -963,11 +963,11 @@ class Endpoint(
963
963
  model = self.di.call_function(where, model=model, **input_output.get_context_for_callables())
964
964
  else:
965
965
  model = model.where(where)
966
- model = model.where_for_request(
966
+ model = model.where_for_request_all(
967
967
  model,
968
+ input_output,
968
969
  input_output.routing_data,
969
970
  input_output.authorization_data,
970
- input_output,
971
971
  overrides=self.column_overrides,
972
972
  )
973
973
  return self.authorization.filter_model(model, input_output.authorization_data, input_output)
@@ -20,12 +20,13 @@ class Cli(InputOutput):
20
20
  super().__init__()
21
21
 
22
22
  def respond(self, response, status_code=200):
23
- if status_code != 200:
24
- sys.exit(response)
25
23
  if type(response) != str:
26
- print(json.dumps(response))
24
+ final = json.dumps(response)
27
25
  else:
28
- print(response)
26
+ final = response
27
+ if status_code != 200:
28
+ sys.exit(final)
29
+ print(final)
29
30
 
30
31
  def get_arguments(self):
31
32
  return sys.argv
clearskies/model.py CHANGED
@@ -983,6 +983,48 @@ class Model(Schema, InjectableProperties):
983
983
  It has no retrun value and is passed no data. By the time this fires the model has already been
984
984
  updated with the new data. You can decide on the necessary actions using the `was_changed` and
985
985
  the `previous_value` functions.
986
+
987
+ ```
988
+ from typing import Any, Self
989
+ import clearskies
990
+
991
+ class History(clearskies.Model):
992
+ id_column_name = "id"
993
+ backend = clearskies.backends.MemoryBackend()
994
+
995
+ id = clearskies.columns.Uuid()
996
+ message = clearskies.columns.String()
997
+ created_at = clearskies.columns.Created(date_format="%Y-%m-%d %H:%M:%S.%f")
998
+
999
+ class User(clearskies.Model):
1000
+ id_column_name = "id"
1001
+ backend = clearskies.backends.MemoryBackend()
1002
+ histories = clearskies.di.inject.ByClass(History)
1003
+
1004
+ id = clearskies.columns.Uuid()
1005
+ age = clearskies.columns.Integer()
1006
+ name = clearskies.columns.String()
1007
+
1008
+ def save_finished(self: Self) -> None:
1009
+ if not self.was_changed("age"):
1010
+ return
1011
+
1012
+ self.histories.create({"message": f"My name is {self.name} and I am {self.age} years old"})
1013
+
1014
+ def my_application(users, histories):
1015
+ jane = users.create({"name": "Jane"})
1016
+ jane.save({"age": 25})
1017
+ jane.save({"age": 26})
1018
+ jane.save({"age": 30})
1019
+
1020
+ return [history.message for history in histories.sort_by("created_at", "ASC")]
1021
+
1022
+ cli = clearskies.contexts.Cli(
1023
+ my_application,
1024
+ classes=[User, History],
1025
+ )
1026
+ cli()
1027
+ ```
986
1028
  """
987
1029
  pass
988
1030
 
@@ -1004,18 +1046,86 @@ class Model(Schema, InjectableProperties):
1004
1046
  """Create a hook to extend so you can provide additional post-delete logic as needed."""
1005
1047
  pass
1006
1048
 
1007
- def where_for_request(
1049
+ def where_for_request_all(
1008
1050
  self: Self,
1009
- models: Self,
1051
+ model: Self,
1052
+ input_output: Any,
1010
1053
  routing_data: dict[str, str],
1011
1054
  authorization_data: dict[str, Any],
1012
- input_output: Any,
1013
1055
  overrides: dict[str, Column] = {},
1014
1056
  ) -> Self:
1015
- """Create a hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler."""
1057
+ """
1058
+ A hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler.
1059
+ """
1016
1060
  for column in self.get_columns(overrides=overrides).values():
1017
- models = column.where_for_request(models, routing_data, authorization_data, input_output) # type: ignore
1018
- return models
1061
+ models = column.where_for_request(model, input_output, routing_data, authorization_data) # type: ignore
1062
+ return self.where_for_request(
1063
+ model, input_output, routing_data=routing_data, authorization_data=authorization_data, overrides=overrides
1064
+ )
1065
+
1066
+ def where_for_request(
1067
+ self: Self,
1068
+ model: Self,
1069
+ input_output: Any,
1070
+ routing_data: dict[str, str],
1071
+ authorization_data: dict[str, Any],
1072
+ overrides: dict[str, Column] = {},
1073
+ ) -> Self:
1074
+ """
1075
+ A hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler.
1076
+
1077
+ Note that this automatically affects the behavior of the various list endpoints, but won't be called when you create your
1078
+ own queries directly. Here's an example where the model restricts the list endpoint so that it only returns users with
1079
+ an age over 18:
1080
+
1081
+ ```
1082
+ from typing import Any, Self
1083
+ import clearskies
1084
+
1085
+ class User(clearskies.Model):
1086
+ id_column_name = "id"
1087
+ backend = clearskies.backends.MemoryBackend()
1088
+ id = clearskies.columns.Uuid()
1089
+ name = clearskies.columns.String()
1090
+ age = clearskies.columns.Integer()
1091
+
1092
+ def where_for_request(
1093
+ self: Self,
1094
+ model: Self,
1095
+ input_output: Any,
1096
+ routing_data: dict[str, str],
1097
+ authorization_data: dict[str, Any],
1098
+ overrides: dict[str, clearskies.Column] = {},
1099
+ ) -> Self:
1100
+ return model.where("age>=18")
1101
+
1102
+ list_users = clearskies.endpoints.List(
1103
+ model_class=User,
1104
+ readable_column_names=["id", "name", "age"],
1105
+ sortable_column_names=["id", "name", "age"],
1106
+ default_sort_column_name="name",
1107
+ )
1108
+
1109
+ wsgi = clearskies.contexts.WsgiRef(
1110
+ list_users,
1111
+ classes=[User],
1112
+ bindings={
1113
+ "memory_backend_default_data": [
1114
+ {
1115
+ "model_class": User,
1116
+ "records": [
1117
+ {"id": "1-2-3-4", "name": "Bob", "age": 20},
1118
+ {"id": "1-2-3-5", "name": "Jane", "age": 17},
1119
+ {"id": "1-2-3-6", "name": "Greg", "age": 22},
1120
+ ],
1121
+ },
1122
+ ]
1123
+ },
1124
+ )
1125
+ wsgi()
1126
+ ```
1127
+ """
1128
+ return model
1019
1129
 
1020
1130
  ##############################################################
1021
1131
  ### From here down is functionality related to list/search ###