Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
with the same effect. </li> <p>
<li><b>spyceRedirect</b> is used by the
<a href="#mod_redirect">redirect</a> module. It causes the
Spyce engine to immediately redirect the request to another Spyce file
<i>internally</i>. Internally means that we do not send back a redirect to
the browser, but merely clear the output buffer and start processing a new
script. </li> <p>
<li><b>All other exceptions</b> that occur at runtime will be processed via
the Spyce <a href="#mod_error">error</a> module. This
module will emit a default error message, unless the user has installed some
other error handler. </li> <p>
</ul><p>
Note that non-runtime exceptions, such as exceptions caused by compile errors,
missing files, access restrictions and the like, are handled by the server.
The default <a href="#runtime_common">server error handler</a>
can be configured via the server configuration file. <p>
<table border=1 align=center>
<tr><td align=left bgcolor="#cccccc">
<font face="arial, helvetica" size="-1"><b>examples/gif.spy</b></font>
</td></tr><tr><td>
<font face=courier>
<pre style='font-family: courier,monospace; font-size: small'><font color="#000000"><b><font color="#CC00CC">[[.import name=include ]]</font>
<font color="#0000CC">[[\
# Spyce can also generate other content types
# The following code displays the Spyce logo
response.setContentType('image/gif')
import os.path, spyce
path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
response.write(include.dump(path, 1))
raise spyceDone
]]</font>
</b></font></pre>
</font>
</td></tr><tr><td align=right bgcolor="#cccccc">
<font face="arial, helvetica" size="-1">
<b><a href="/docs/examples/gif.spy">Run this code</a></b>
</font>
</td></tr></table>
<p>
<big><a name="runtime_transform"></a><b>3.2. <font color=#ee0000>Code Transformation</font></b></big><p>
While the minutia of the code transformation that produces Python code from
the Spyce sources is of no interest to the casual user, it has some slight,
but important, ramifications on certain aspects of the Python language
semantics when used inside a Spyce file. <p>
The result of the Spyce compilation is some Python code, wherein the majority
of the Spyce code actually resides in a single function called
<b>spyceProcess</b>. If you are curious to see the result of a Spyce
compilation, execute: <font face=courier>"spyce -c".</font> <p>
It follows from the compilation transformation that:
<ul>
<li>Any functions defined within the Spyce file are actually nested
functions within the spyceProcess function. </li> <p>
<li>The use of <b>global</b> variables within Spyce code is not supported,
but also not needed. If nested scoping is available (Python versions
>2.1) then these variables will simply be available. If not, then you
will need to pass variables into functions as default parameters, and will
not be able to update them by value (standard Python limitations). It is
good practice to store constants and other globals in a single class, or to
to place them in a single, included file, or both. </li> <p>
<li><p>The global Spyce namespace is reserved for special variables, such as
Spyce and Python modules. While the use of the keyword <font
face=courier>global</font> is not explicitly checked, it will pollute this
space and may result in unexpected behaviour or runtime errors. </li> <p>
<li>The lifetime of variables is the duration of a request. Variables with
lifetimes longer than a single request can be stored using the <a
href="#mod_pool">pool</a> module. </li> <p>
</ul> <p>
<big><a name="runtime_web"></a><b>3.3. <font color=#ee0000>Dynamic Content</font></b></big><p>
The most common use of Spyce is to serve dynamic HTML content, but it should
be noted that Spyce can be used as a general purpose text engine. It can be
used to generate XML, text and other output, as easily as HTML. In fact, the
engine can also be used to generate dynamic binary data, such as images, PDF
files, etc., if needed. <p>
The Spyce engine can be installed
in a number of different configurations that can produce dynamic output.
Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is
slower, since a new engine must be created for each request. See the
<a href="#conf">configuration</a> section for details.
<big><a name="runtime_static"></a><b>3.4. <font color=#ee0000>Static Content</font></b></big><p>
A nice feature of Spyce is that it can be invoked both from within a web
server to process a web request dynamically and also from the command-line.
The processing engine itself is the same in both cases. The command-line
option is actually just a modified CGI client, and is often used to
pre-process static content, such as this manual. <p>
Some remarks regarding command-line execution specifics are in order. The
request and response objects for a command-line request are connected to
standard input and output, as expected. A minimal CGI-like environment is
created among the other shell environment variables. Header and cookie lookups
will return None and the engine will accept input on stdin for POST
information, if requested. There is also no compiler cache, since the process
memory is lost at the end of every execution. <p>
Most commonly, Spyce is invoked from the command-line to generate static .html
ouput. Spyce then becomes a rather handy and powerful .html preprocessing
tool. It was used on this documentation to produce the consistent headers and
footers, to include and highlight the example code snippets, etc...<p>
The following makefile rule comes in handy:<p>
<table align=center border=0><tr><td><font face=courier>
<b><pre>
%.html: %.spy
spyce -o $@ $<
</pre></b>
</font></td></tr></table><p>
<big><a name="runtime_cmdline"></a><b>3.5. <font color=#ee0000>Command line</font></b></big><p>
The full command-line syntax is:<p>
<table align=center border=0><tr><td>
<font face=courier size=-1>
<b><pre>Spyce 2.1
Command-line usage:
spyce -c [-o filename.html] <filename.spy>
spyce -w <filename.spy> <-- CGI
spyce -O filename(s).spy <-- batch process
spyce -l [-d file ] <-- proxy server
spyce -h | -v
-h, -?, --help display this help information
-v, --version display version
-o, --output send output to given file
-O send outputs of multiple files to *.html
-c, --compile compile only; do not execute
-w, --web cgi mode: emit headers (or use run_spyceCGI.py)
-q, --query set QUERY_STRING environment variable
-l, --listen run in HTTP server mode
-d, --daemon run as a daemon process with given pidfile
--conf [file] Spyce configuration file
To configure Apache, please refer to: spyceApache.conf
For more details, refer to the documentation.
http://spyce.sourceforge.net
Send comments, suggestions and bug reports to <rimon-AT-acm.org>.
</pre></b>
</font>
</td></tr></table>
<p>
<big><a name="runtime_common"></a><b>3.6. <font color=#ee0000>Configuration</font></b></big><p>
Since there are a variety of very different
<a href="#conf">installation</a>
alternatives for the Spyce
engine, effort has been invested in consolidating all the various runtime
configuration options. By default, the Spyce engine will search for a file
called <b>spyceconf.py</b> in its installation directory. An alternative file
location may be specified via the <b>--conf</b> command-line option.
<p>
The spyce configuration file is a valid python module; any python code
may be used. To avoid duplication, we recommend starting with
"<font face=courier>from spyceconf import *</font>" and
override only select settings. One thing you cannot do from the config module
is access the Spyce server object <font face=courier>spyce.getServer()</font>,
since it has not been initialized yet. <p>
You may access the loaded configuration module from Spyce scripts and from
Python modules using the <font face=courier>config</font> attribute of the
server object. Or, simply as the <font face=courier>spyceConfig</font> module,
regardless of it actual file name. For example: <p>
<table border=1 align=center>
<tr><td align=left bgcolor="#cccccc">
<font face="arial, helvetica" size="-1"><b>examples/config.spy</b></font>
</td></tr><tr><td>
<font face=courier>
<pre style='font-family: courier,monospace; font-size: small'><font color="#000000"><b><font color="#0000CC">[[\
import spyceConfig
home = spyceConfig.SPYCE_HOME
]]</font>
<font color="#CC0000">[[= home ]]</font>
</b></font></pre>
</font>
</td></tr><tr><td align=right bgcolor="#cccccc">
<font face="arial, helvetica" size="-1">
<b><a href="/docs/examples/config.spy">Run this code</a></b>
</font>
</td></tr></table>
<p>
Below is the configuration file that this server is running. The length of the
file is primarily due to the thoroughness of the comments: <p>
<table class=code align=center><tr><td>
<style>
.STRING {
color: #a0522d;
}
.COMMENT {
color: #ff7448;
font-family: serif;
}
.KEYWORD {
color: #9e79af;
}
.FUNCTION {
color: #0074ef;
}
</style>
<pre>
<span class="COMMENT"># NOTE: do note write code that directly imports this module
</span><span class="COMMENT"># (except when you are writing a custom configuration module.)
</span><span class="COMMENT"># This is a recipe for trouble, since spyce allows the user
</span><span class="COMMENT"># to specify the configuration module filename on the commandline.
</span><span class="COMMENT"># Instead, use import spyce; spyce.getServer().config.
</span><span class="NL">
</span><span class="KEYWORD">import</span> <span class="NAME">os</span><span class="OP">,</span> <span class="NAME">sys</span><span class="NEWLINE">
</span><span class="KEYWORD">import</span> <span class="NAME">spycePreload</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># Determine SPYCE_HOME dynamically.
</span><span class="COMMENT"># (you can hardcode SPYCE_HOME if you really want to, but it shouldn't be necessary.)
</span><span class="NAME">SPYCE_HOME</span> <span class="OP">=</span> <span class="NAME">spycePreload</span><span class="OP">.</span><span class="NAME">guessSpyceHome</span><span class="OP">(</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The spyce path determines which directories are searched for when
</span><span class="COMMENT"># loading modules (with [[.import]]) and tag libraries (with [[.taglib]]
</span><span class="COMMENT"># and the globaltags configuration later in this file.
</span><span class="COMMENT">#
</span><span class="COMMENT"># By default, the Spyce installation directory is always searched
</span><span class="COMMENT"># first. Any directories in the SPYCE_PATH environment are also
</span><span class="COMMENT"># searched.
</span><span class="COMMENT">#
</span><span class="COMMENT"># If you need to import from .py modules in nonstandard locations
</span><span class="COMMENT"># (i.e., not in your python installation's library directory),
</span><span class="COMMENT"># you will want to add their directories to sys.path as well, as
</span><span class="COMMENT"># done here for the error module. (However, Spyce automagically
</span><span class="COMMENT"># changes sys.path dynamically so you will always be able to import
</span><span class="COMMENT"># modules in the same directory as your currently-processing .spy file.)
</span><span class="COMMENT">#
</span><span class="COMMENT"># path += ['/usr/spyce/inc/myapplication', '/var/myapp/lib']
</span><span class="NAME">path</span> <span class="OP">=</span> <span class="OP">[</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'modules'</span><span class="OP">)</span><span class="OP">,</span> <span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'contrib'</span><span class="OP">,</span> <span class="STRING">'modules'</span><span class="OP">)</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">append</span><span class="OP">(</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'tags'</span><span class="OP">)</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">append</span><span class="OP">(</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'contrib'</span><span class="OP">,</span> <span class="STRING">'tags'</span><span class="OP">)</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="KEYWORD">if</span> <span class="NAME">os</span><span class="OP">.</span><span class="NAME">environ</span><span class="OP">.</span><span class="NAME">has_key</span><span class="OP">(</span><span class="STRING">'SPYCE_PATH'</span><span class="OP">)</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="NAME">path</span> <span class="OP">+=</span> <span class="NAME">os</span><span class="OP">.</span><span class="NAME">environ</span><span class="OP">[</span><span class="STRING">'SPYCE_PATH'</span><span class="OP">]</span><span class="OP">.</span><span class="NAME">split</span><span class="OP">(</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">pathsep</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="COMMENT"># provide originalsyspath so if someone wants to maintain a config file via
</span><span class="COMMENT"># "from spyceconf import *"
</span><span class="COMMENT"># he can remove the above modifications if desired.
</span><span class="DEDENT"></span><span class="NAME">originalsyspath</span> <span class="OP">=</span> <span class="NAME">list</span><span class="OP">(</span><span class="NAME">sys</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NAME">sys</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">extend</span><span class="OP">(</span><span class="NAME">path</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The globaltags option specifies a group of tag libraries that will be autoloaded
</span><span class="COMMENT"># as if [[.taglib name=libname from=file as=prefix]] were specified in every .spy
</span><span class="COMMENT"># file. (There is no performance hit if the library is not used on a page;
</span><span class="COMMENT"># the Spyce compiler optimizes it out.)
</span><span class="COMMENT">#
</span><span class="COMMENT"># The format is ('libname', 'file', 'prefix').
</span><span class="COMMENT"># For a 2.0-style tag library, the libname attribute is ignored. Passing None is fine.
</span><span class="COMMENT">#
</span><span class="COMMENT"># globaltags.append(('mytag', 'mytaglib.py', 'my'))
</span><span class="COMMENT"># globaltags.append((None, 'taglib2.py', 'my2'))
</span><span class="NAME">globaltags</span> <span class="OP">=</span> <span class="OP">[</span><span class="NL">
</span> <span class="OP">(</span><span class="STRING">'core'</span><span class="OP">,</span> <span class="STRING">'core.py'</span><span class="OP">,</span> <span class="STRING">'spy'</span><span class="OP">)</span><span class="OP">,</span><span class="NL">
</span> <span class="OP">(</span><span class="STRING">'form'</span><span class="OP">,</span> <span class="STRING">'form.py'</span><span class="OP">,</span> <span class="STRING">'f'</span><span class="OP">)</span><span class="OP">,</span><span class="NL">
</span> <span class="OP">(</span><span class="NAME">None</span><span class="OP">,</span> <span class="STRING">'render.spi'</span><span class="OP">,</span> <span class="STRING">'render'</span><span class="OP">)</span><span class="OP">,</span><span class="NL">
</span> <span class="OP">]</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The default parent template is the one that is used by <spy:parent>
</span><span class="COMMENT"># if no src attribute is given, specified as an absolute url.
</span><span class="NAME">defaultparent</span> <span class="OP">=</span> <span class="STRING">"/parent.spi"</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The errorhandler option sets the server-level error handler. These
</span><span class="COMMENT"># errors include spyce.spyceNotFound, spyce.spyceForbidden,
</span><span class="COMMENT"># spyce.spyceSyntaxError and spyce.pythonSyntaxError. (file-level
</span><span class="COMMENT"># error handling is defined within Spyce scripts using the error module.)
</span><span class="COMMENT">#
</span><span class="COMMENT"># The server will call the error handler as errorhandler(request, response, error).
</span><span class="COMMENT">#
</span><span class="COMMENT"># Please look at the default function if you are considering writing your own
</span><span class="COMMENT"># server error handler.
</span><span class="KEYWORD">import</span> <span class="NAME">error</span><span class="NEWLINE">
</span><span class="NAME">errorhandler</span> <span class="OP">=</span> <span class="NAME">error</span><span class="OP">.</span><span class="NAME">serverHandler</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The pageerror option sets the default page-level error handler.
</span><span class="COMMENT"># "Page-level" means all runtime errors that occur during the
</span><span class="COMMENT"># processing of a Spyce script (i.e. after the compilation phase has
</span><span class="COMMENT"># completed successfully)
</span><span class="COMMENT">#
</span><span class="COMMENT"># The format of this option is one of:
</span><span class="COMMENT"># ('string', 'MODULE', 'VARIABLE')
</span><span class="COMMENT"># ('file', 'URL')
</span><span class="COMMENT"># (This format is used since the error template must be transformed into
</span><span class="COMMENT"># compiled spyce code, which can't be done before the configuration file
</span><span class="COMMENT"># is completely loaded.)
</span><span class="COMMENT">#
</span><span class="COMMENT"># Please refer to the default template to see how to define your own
</span><span class="COMMENT"># page-level error handlers.
</span><span class="COMMENT">#
</span><span class="COMMENT"># pageerrortemplate = ('file', '/error.spy')
</span><span class="NAME">pageerrortemplate</span> <span class="OP">=</span> <span class="OP">(</span><span class="STRING">'string'</span><span class="OP">,</span> <span class="STRING">'error'</span><span class="OP">,</span> <span class="STRING">'defaultErrorTemplate'</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The cache option affects the underlying cache mechanism that the
</span><span class="COMMENT"># server uses to maintain compiled Spyce scripts. Currently, Spyce
</span><span class="COMMENT"># supports two cache handlers:
</span><span class="COMMENT">#
</span><span class="COMMENT"># cache = 'memory'
</span><span class="COMMENT"># OR
</span><span class="COMMENT"># cache = 'file'
</span><span class="COMMENT"># cachedir = '/tmp' # REQUIRED: directory in which to store compiled files
</span><span class="COMMENT">#
</span><span class="COMMENT"># Why store the cache in the filesystem instead of memory? The main
</span><span class="COMMENT"># reason is if you are running under CGI or mod_python. Under
</span><span class="COMMENT"># mod_python, Apache will kick off a number of separate spyce
</span><span class="COMMENT"># processes; each would have its own memory cache; using the
</span><span class="COMMENT"># filesystem avoids wasteful duplication. (Pointing the cachedir to a
</span><span class="COMMENT"># ramdisk makes it almost as fast as a memory cache.) Under CGI of
</span><span class="COMMENT"># course, the python process isn't persistent so file caching is the
</span><span class="COMMENT"># only option to avoid expensive recompilation with each request.
</span><span class="COMMENT">#
</span><span class="COMMENT"># If you are running multiple Spyce instances on the same machine,
</span><span class="COMMENT"># they cannot share the same cachedir. Give each a different cachedir,
</span><span class="COMMENT"># or use the in-memory cache type.
</span><span class="NAME">cache</span> <span class="OP">=</span> <span class="STRING">'memory'</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The check_mtime option affects the caching of compiled Spyce code.
</span><span class="COMMENT"># When True, Spyce will check file timestamps with each request and
</span><span class="COMMENT"># recompile if they have been modified. Setting this to False can
</span><span class="COMMENT"># speed up a "production server" but you will have to restart the
</span><span class="COMMENT"># server and (if using a file cache) clear out the cachedir
</span><span class="COMMENT"># to have changes made in the spyce code take effect.
</span><span class="NAME">check_mtime</span> <span class="OP">=</span> <span class="NAME">True</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The debug option turns on a LOT of logging to stderr.
</span><span class="NAME">debug</span> <span class="OP">=</span> <span class="NAME">False</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The globals section defines server-wide constants. The hashtable is
</span><span class="COMMENT"># accessible as "pool" within any Spyce file (with the pool
</span><span class="COMMENT"># method loaded), or as self._api.getServerGlobals() within any Spyce
</span><span class="COMMENT"># module.
</span><span class="COMMENT">#
</span><span class="COMMENT"># globals = {'name': "My Website", 'four': 2+2}
</span><span class="NAME">globals</span> <span class="OP">=</span> <span class="OP">{</span><span class="OP">}</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># You may wish to pre-load various Python modules during engine initialization.
</span><span class="COMMENT"># Once imported, they will be in the python module cache.
</span><span class="COMMENT">#
</span><span class="COMMENT"># (You may of course use normal imports at any time in this configuration script;
</span><span class="COMMENT"># however, imports specified here are run after the Spyce server is
</span><span class="COMMENT"># completely initialized, making it safe to access the server internals via
</span><span class="COMMENT"># import spyce; spyce.getServer()...)
</span><span class="COMMENT">#
</span><span class="COMMENT"># imports = ['myModule', 'myModule2']
</span><span class="NAME">imports</span> <span class="OP">=</span> <span class="OP">[</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The root option defines the path from which Spyce requests are processed.
</span><span class="COMMENT"># I.e., when a request for http://yourserver/path/foo.spy arrives,
</span><span class="COMMENT"># Spyce looks for foo.spy in <root>/path/.
</span><span class="COMMENT">#
</span><span class="COMMENT"># root = '/var/www/html'
</span><span class="NAME">root</span> <span class="OP">=</span> <span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'www'</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="COMMENT"># feel free to comment this next line out if you don't have python modules (.py) in your web root
</span><span class="NAME">sys</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">append</span><span class="OP">(</span><span class="NAME">root</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># some parts of spyce may need to create temporary files; usually the default is fine.
</span><span class="COMMENT"># BUT if you do override this, be sure to also override other parts of the config
</span><span class="COMMENT"># that reference it. (currently just session_store)
</span><span class="KEYWORD">import</span> <span class="NAME">tempfile</span><span class="NEWLINE">
</span><span class="NAME">tmp</span> <span class="OP">=</span> <span class="NAME">tempfile</span><span class="OP">.</span><span class="NAME">gettempdir</span><span class="OP">(</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># active tag to render form validation errors
</span><span class="NAME">validation_render</span> <span class="OP">=</span> <span class="STRING">'render:validation'</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="NL">
</span><span class="COMMENT">#####
</span><span class="COMMENT"># database connection
</span><span class="COMMENT">#####
</span><span class="NL">
</span><span class="KEYWORD">from</span> <span class="NAME">sqlalchemy</span><span class="OP">.</span><span class="NAME">ext</span><span class="OP">.</span><span class="NAME">sqlsoup</span> <span class="KEYWORD">import</span> <span class="NAME">SqlSoup</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># Examples:
</span><span class="COMMENT"># db = SqlSoup('postgres://user:pass@localhost/dbname')
</span><span class="COMMENT"># db = SqlSoup('sqlite:///my.db')
</span><span class="COMMENT"># db = SqlSoup('mysql://user:pass@localhost/dbname')
</span><span class="COMMENT">#
</span><span class="COMMENT"># SqlSoup takes the same URLs as an SqlAlchemy Engine. See
</span><span class="COMMENT"># http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing
</span><span class="COMMENT"># for more examples.
</span><span class="KEYWORD">try</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="NAME">db</span> <span class="OP">=</span> <span class="NAME">SqlSoup</span><span class="OP">(</span><span class="STRING">'sqlite:///www/demos/to-do/todo.db'</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="DEDENT"></span><span class="KEYWORD">except</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="NAME">db</span> <span class="OP">=</span> <span class="NAME">None</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT">#####
</span><span class="COMMENT"># session options -- see docs/mod_session.html for details
</span><span class="COMMENT">#####
</span><span class="NL">
</span><span class="DEDENT"></span><span class="KEYWORD">import</span> <span class="NAME">session</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="NAME">session_store</span> <span class="OP">=</span> <span class="NAME">session</span><span class="OP">.</span><span class="NAME">DbmStore</span><span class="OP">(</span><span class="NAME">tmp</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="COMMENT"># session_store = session.MemoryStore()
</span><span class="NL">
</span><span class="NAME">session_path</span> <span class="OP">=</span> <span class="STRING">'/'</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="NAME">session_expire</span> <span class="OP">=</span> <span class="NUMBER">24</span> <span class="OP">*</span> <span class="NUMBER">60</span> <span class="OP">*</span> <span class="NUMBER">60</span> <span class="COMMENT"># seconds</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT">#####
</span><span class="COMMENT"># login options
</span><span class="COMMENT">#####
</span><span class="NL">
</span><span class="COMMENT"># The spyce login system uses the session storage
</span><span class="COMMENT"># defined above; a key called _spy_login will be added to each session.
</span><span class="NL">
</span><span class="COMMENT"># validators must be a function that takes login and password as
</span><span class="COMMENT"># arguments, and returns a pickle-able object (usually int or string)
</span><span class="COMMENT"># representing the ID of the logged in user, or None if login fails.
</span><span class="COMMENT">#
</span><span class="COMMENT"># You'll need to supply your own to hook into your database or other
</span><span class="COMMENT"># validation system; see the pyweboff config.py for an example of doing
</span><span class="COMMENT"># this.
</span><span class="KEYWORD">def</span> <span class="FUNCTION">nevervalidator</span><span class="OP">(</span><span class="NAME">login</span><span class="OP">,</span> <span class="NAME">password</span><span class="OP">)</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="KEYWORD">return</span> <span class="NAME">None</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="DEDENT"></span><span class="KEYWORD">def</span> <span class="FUNCTION">testvalidator</span><span class="OP">(</span><span class="NAME">login</span><span class="OP">,</span> <span class="NAME">password</span><span class="OP">)</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="KEYWORD">if</span> <span class="NAME">login</span> <span class="OP">==</span> <span class="STRING">'spyce'</span> <span class="KEYWORD">and</span> <span class="NAME">password</span> <span class="OP">==</span> <span class="STRING">'spyce'</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="KEYWORD">return</span> <span class="NUMBER">2</span><span class="NEWLINE">
</span> <span class="DEDENT"></span><span class="KEYWORD">return</span> <span class="NAME">None</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="DEDENT"></span><span class="NAME">login_defaultvalidator</span> <span class="OP">=</span> <span class="NAME">testvalidator</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># How to store login tokens. FileStorage comes with Spyce,
</span><span class="COMMENT"># but it's easy to create your own Storage class if you want to put them
</span><span class="COMMENT"># in your database, for instance.
</span><span class="KEYWORD">from</span> <span class="NAME">_coreutil</span> <span class="KEYWORD">import</span> <span class="NAME">FileStorage</span><span class="NEWLINE">
</span><span class="COMMENT"># It's not a good idea to put login-tokens off of www/ in production!
</span><span class="COMMENT"># It's just done this way here to keep the spyce source tree relatively clean.
</span><span class="NAME">login_storage</span> <span class="OP">=</span> <span class="NAME">FileStorage</span><span class="OP">(</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'www'</span><span class="OP">,</span> <span class="STRING">'login-tokens'</span><span class="OP">)</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># tags to render login form; must be visible in the globaltags search space.
</span><span class="COMMENT"># These come from tags/render.spi; to make your own, just create a tag
</span><span class="COMMENT"># that takes the same parameters and add the library to the globaltags list above.
</span><span class="NAME">login_render</span> <span class="OP">=</span> <span class="STRING">'render:login'</span><span class="NEWLINE">
</span><span class="NAME">loginrequired_render</span> <span class="OP">=</span> <span class="STRING">'render:login_required'</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT">######
</span><span class="COMMENT"># webserver options -- does not affect mod_python or *CGI configurations
</span><span class="COMMENT">######
</span><span class="NL">
</span><span class="COMMENT"># indexFiles specifies a list of files to look for
</span><span class="COMMENT"># in a directory if directory itself is requested.
</span><span class="COMMENT"># The first matching file will be selected.
</span><span class="COMMENT"># If empty, a directory listing will be served instead.
</span><span class="COMMENT">#
</span><span class="COMMENT"># indexFiles = ['index.spy', 'index.html', 'index.txt']
</span><span class="NAME">indexFiles</span> <span class="OP">=</span> <span class="OP">[</span><span class="STRING">'index.spy'</span><span class="OP">,</span> <span class="STRING">'index.html'</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The Spyce webserver uses a threaded concurrency model. (Historically,
</span><span class="COMMENT"># it also offered "no concurrency" and forking. If for some strange
</span><span class="COMMENT"># reason you really want no concurrency, set minthreads=maxthreads=1.
</span><span class="COMMENT"># Forking was removed entirely because it performed over 10x slower
</span><span class="COMMENT"># than threading on Linux and Windows.)
</span><span class="COMMENT">#
</span><span class="COMMENT"># Do note that because of the Python GIL (global interpeter lock),
</span><span class="COMMENT"># only one CPU of a multi-CPU machine can execute Python code at a time.
</span><span class="COMMENT"># If this is your situation, mod_python may be a better option for you.
</span><span class="NAME">minthreads</span> <span class="OP">=</span> <span class="NUMBER">5</span><span class="NEWLINE">
</span><span class="NAME">maxthreads</span> <span class="OP">=</span> <span class="NUMBER">10</span><span class="NEWLINE">
</span><span class="COMMENT"># number of pending requests to accept
</span><span class="NAME">maxqueuesize</span> <span class="OP">=</span> <span class="NUMBER">50</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># Restart the webserver if a python module changes.
</span><span class="COMMENT"># Spyce does this by running the "real" server in a subprocess; when that
</span><span class="COMMENT"># server detects changed modules, it exits and Spyce starts another one.
</span><span class="COMMENT">#
</span><span class="COMMENT"># Spyce will check for changes every second, or when a request
</span><span class="COMMENT"># is made, whichever comes first.
</span><span class="COMMENT">#
</span><span class="COMMENT"># It's highly recommended to turn this off for "production" servers,
</span><span class="COMMENT"># since checking each module for each request is a performance hit.
</span><span class="NAME">check_modules_and_restart</span> <span class="OP">=</span> <span class="NAME">True</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The ipaddr option defines which IP addresses the server will listen on.
</span><span class="COMMENT"># empty string for all.
</span><span class="NAME">ipaddr</span> <span class="OP">=</span> <span class="STRING">''</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The port option defines which TCP port the server will listen on.
</span><span class="NAME">port</span> <span class="OP">=</span> <span class="NUMBER">8000</span><span class="NEWLINE">
</span><span class="COMMENT"># Port that provides an interactive Python console interface to
</span><span class="COMMENT"># the webserver's guts. Currently no password protection is offered;
</span><span class="COMMENT"># don't expose this to the outside world!
</span><span class="NAME">adminport</span> <span class="OP">=</span> <span class="NAME">None</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The mime option is a list of filenames. The files should
</span><span class="COMMENT"># be definitions of mime-types for common file extensions in the
</span><span class="COMMENT"># standard Apache format.
</span><span class="COMMENT">#
</span><span class="COMMENT"># mime: ['/etc/mime.types']
</span><span class="NAME">mime</span> <span class="OP">=</span> <span class="OP">[</span><span class="NAME">os</span><span class="OP">.</span><span class="NAME">path</span><span class="OP">.</span><span class="NAME">join</span><span class="OP">(</span><span class="NAME">SPYCE_HOME</span><span class="OP">,</span> <span class="STRING">'spyce.mime'</span><span class="OP">)</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># The www_handlers option defines the hander used for files of
</span><span class="COMMENT"># arbitrary extensions. (The None key specifies the default.)
</span><span class="COMMENT"># The currently supported handlers are:
</span><span class="COMMENT"># spyce - process the file at the requested path as a spyce script
</span><span class="COMMENT"># directory - display directory listing
</span><span class="COMMENT"># dump - transfer the file at the requested path verbatim,
</span><span class="COMMENT"># providing an appropriate "Content-type" header, if it is known.
</span><span class="COMMENT"># (It's difficult to use the actual instance methods here since we
</span><span class="COMMENT"># don't have a handle to the WWW server object. So, we use strings
</span><span class="COMMENT"># and let spyceWWW eval them later.)
</span><span class="NAME">www_handlers</span> <span class="OP">=</span> <span class="OP">{</span><span class="NL">
</span> <span class="STRING">'spy'</span><span class="OP">:</span> <span class="STRING">'spyce'</span><span class="OP">,</span><span class="NL">
</span> <span class="STRING">'/'</span><span class="OP">:</span> <span class="STRING">'directory'</span><span class="OP">,</span><span class="NL">
</span> <span class="NAME">None</span><span class="OP">:</span> <span class="STRING">'dump'</span><span class="NL">
</span><span class="OP">}</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="NL">
</span><span class="COMMENT">######
</span><span class="COMMENT"># (F)CGI options
</span><span class="COMMENT">######
</span><span class="NL">
</span><span class="COMMENT"># Forbid direct requests to the cgi script and discard command line arguments.
</span><span class="COMMENT">#
</span><span class="COMMENT"># Reason: http://www.cert.org/advisories/CA-1996-11.html
</span><span class="COMMENT">#
</span><span class="COMMENT"># You may need to disable this for
</span><span class="COMMENT"># 1) non-Apache servers that do not set the REDIRECT_STATUS environment
</span><span class="COMMENT"># variable.
</span><span class="COMMENT"># 2) when using the alternative #! variant of CGI configuration
</span><span class="NAME">cgi_allow_only_redirect</span> <span class="OP">=</span> <span class="NAME">False</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="NL">
</span><span class="COMMENT">######
</span><span class="COMMENT"># standard module customization
</span><span class="COMMENT">######
</span><span class="NL">
</span><span class="COMMENT"># (request module)
</span><span class="NL">
</span><span class="COMMENT"># param filters and file filters are lists of functions that are called
</span><span class="COMMENT"># for all GET and POST parameters or files, respectively.
</span><span class="COMMENT"># Each callable should expect two arguments: a reference to the
</span><span class="COMMENT"># request object/spyce module, and the dictionary being filtered.
</span><span class="COMMENT"># (Thus, each callable in param_filters will be called twice; once for the
</span><span class="COMMENT"># GET dict and once for POST. Each callable in file_filters will only be called once.)
</span><span class="NAME">param_filters</span> <span class="OP">=</span> <span class="OP">[</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="NAME">file_filters</span> <span class="OP">=</span> <span class="OP">[</span><span class="OP">]</span><span class="NEWLINE">
</span><span class="ENDMARKER"></span>
</pre>
</td></tr></table>
<p>
<big><a name="runtime_util"></a><b>3.7. <font color=#ee0000>Server utilities</font></b></big><p>
Like any application server, the Spyce server provides several facilities
that can aid development.
<big><a name="runtime_util_scheduler"></a><b>3.7.1. <font color=#ee0000><i>The Spyce scheduler</i></font></b></big><p>
Spyce provides a scheduler that allows you to easily define tasks to
run at specified times or intervals inside the Spyce server. This
allows your tasks to leverage the tools Spyce gives you, as well as
any global data your application maintains within Spyce, such as
cached data or database connection pools. This also has the advantage
(over, say, crontab entries) of keeping your application self-contained,
making it easier to deploy changes from a development machine to production.
<p>
<i>The Spyce scheduler is currently only useful if you are running
in webserver mode. If you run under mod_python, CGI, or FastCGI,
you could approximate scheduler behavior by storing tasks and checking
to see if any are overdue with every request received; this would
be an excellent project for someone wishing to get started in Spyce
development.</i>
<p>
<div class=code>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom> <br>
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong>scheduler</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///Y|/develop/pcwprojects/pcwpdfconvert/spyce-2.1/scheduler.py">y:\develop\pcwprojects\pcwpdfconvert\spyce-2.1\scheduler.py</a></font></td></tr></table>
<p><tt>A module for scheduling arbitrary callables to run at given times<br>
or intervals, modeled on the naviserver API. Scheduler runs in<br>
its own thread; callables run in this same thread, so if you have<br>
an unusually long callable to run you may wish to give it its own<br>
thread, for instance,<br>
<br>
<a href="#-schedule">schedule</a>(3600, lambda: threading.Thread(target=longcallable).start())<br>
<br>
Scheduler does not provide subsecond resolution.<br>
<br>
Public functions are threadsafe.</tt></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom> <br>
<font color="#fffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="atexit.html">atexit</a><br>
<a href="sys.html">sys</a><br>
</td><td width="25%" valign=top><a href="threading.html">threading</a><br>
<a href="time.html">time</a><br>
</td><td width="25%" valign=top><a href="traceback.html">traceback</a><br>
</td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom> <br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
<tr><td bgcolor="#ee77aa"><tt> </tt></td><td> </td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="scheduler.html#Task">Task</a>
</font></dt></dl>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom> <br>
<font color="#000000" face="helvetica, arial"><a name="Task">class <strong>Task</strong></a></font></td></tr>
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
<td colspan=2><tt>Instantiated by the schedule methods.<br>
<br>
Instance variables:<br>
nextrun: epoch seconds at which to run next<br>
interval: seconds before repeating<br>
callable: function to invoke<br>
last: if True, will be unscheduled after nextrun<br>
<br>
(Note that by manually setting last on a <a href="#Task">Task</a> instance, you<br>
can cause it to run an arbitrary number of times.)<br> </tt></td></tr>
<tr><td> </td>
<td width="100%">Methods defined here:<br>
<dl><dt><a name="Task-__init__"><strong>__init__</strong></a>(self, firstrun, interval, callable, once)</dt></dl>
<dl><dt><a name="Task-__repr__"><strong>__repr__</strong></a>(self)</dt></dl>
</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#eeaa77">
<td colspan=3 valign=bottom> <br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
<tr><td bgcolor="#eeaa77"><tt> </tt></td><td> </td>
<td width="100%"><dl><dt><a name="-debuglogger"><strong>debuglogger</strong></a>(s)</dt></dl>
<dl><dt><strong>logger</strong> <em>lambda</em> s</dt></dl>
<dl><dt><a name="-pause"><strong>pause</strong></a>()</dt><dd><tt>Temporarily suspend running scheduled tasks</tt></dd></dl>
<dl><dt><a name="-schedule"><strong>schedule</strong></a>(interval, callable, once<font color="#909090">=False</font>)</dt><dd><tt>Schedules callable to be run every interval seconds.<br>
Returns the scheduled <a href="#Task">Task</a> object.</tt></dd></dl>
<dl><dt><a name="-schedule_daily"><strong>schedule_daily</strong></a>(hours, minutes, callable, once<font color="#909090">=False</font>)</dt><dd><tt>Schedules callable to be run at hours:minutes every day.<br>
(Hours is a 24-hour format.)<br>
Returns the scheduled <a href="#Task">Task</a> object.</tt></dd></dl>
<dl><dt><a name="-schedule_weekly"><strong>schedule_weekly</strong></a>(day, hours, minutes, callable, once<font color="#909090">=False</font>)</dt><dd><tt>Schedules callable to be run at hours:minutes on the given<br>
zero-based day of the week. (Monday is 0.)<br>
Returns the scheduled <a href="#Task">Task</a> object.</tt></dd></dl>
<dl><dt><a name="-unpause"><strong>unpause</strong></a>()</dt><dd><tt>Resume running scheduled tasks. If a task came due while<br>
it was paused, it will run immediately after unpausing.</tt></dd></dl>
<dl><dt><a name="-unschedule"><strong>unschedule</strong></a>(task)</dt><dd><tt>Removes the given task from the scheduling queue.</tt></dd></dl>
</td></tr></table>
</div>
<table border=1 align=center>
<tr><td align=left bgcolor="#cccccc">
<font face="arial, helvetica" size="-1"><b>examples/scheduling.py</b></font>
</td></tr><tr><td>
<font face=courier>
<style>
.STRING {
color: #a0522d;
}
.COMMENT {
color: #ff7448;
font-family: serif;
}
.KEYWORD {
color: #9e79af;
}
.FUNCTION {
color: #0074ef;
}
</style>
<pre>
<span class="KEYWORD">import</span> <span class="NAME">spyce</span><span class="OP">,</span> <span class="NAME">scheduler</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="KEYWORD">def</span> <span class="FUNCTION">delete_unsubmitted</span><span class="OP">(</span><span class="OP">)</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="NAME">db</span> <span class="OP">=</span> <span class="NAME">spyce</span><span class="OP">.</span><span class="NAME">SPYCE_GLOBALS</span><span class="OP">[</span><span class="STRING">'dbpool'</span><span class="OP">]</span><span class="OP">.</span><span class="NAME">connection</span><span class="OP">(</span><span class="OP">)</span><span class="NEWLINE">
</span> <span class="NAME">sql</span> <span class="OP">=</span> <span class="STRING">"DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"</span><span class="NEWLINE">
</span> <span class="NAME">db</span><span class="OP">.</span><span class="NAME">execute</span><span class="OP">(</span><span class="NAME">sql</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="NL">
</span><span class="COMMENT"># delete alerts that were created over a week ago but but still not submitted
</span><span class="DEDENT"></span><span class="NAME">scheduler</span><span class="OP">.</span><span class="NAME">schedule_daily</span><span class="OP">(</span><span class="NUMBER">00</span><span class="OP">,</span> <span class="NUMBER">10</span><span class="OP">,</span> <span class="NAME">delete_unsubmitted</span><span class="OP">)</span><span class="NEWLINE">
</span><span class="ENDMARKER"></span>
</pre>
</font>
</td></tr></table>
<big><a name="runtime_util_su"></a><b>3.7.2. <font color=#ee0000><i>spyceUtil</i></font></b></big><p>
Most of the spyceUtil module is interesting only to internal operations,
but several functions are more generally applicable:
<ul>
<li> <b>url2file</b>( url, relativeto=None )
<br>Returns the filesystem path of the file represented by url, relative
to a given path. For example, url2file('/index.spy') or
url2file('img/header.png', request.filename()).
</li> <p>
<li> <b>exceptionString</b>( )
<br>Every python programmer writes this eventually: returns a string containing
the description and stacktrace for the most recent exception.
</li> <p>
</ul> <p>
<big><a name="mod"></a><b>3.8. <font color=#ee0000>Modules</font></b></big><p>
The Spyce language, as described above, is simple and small. Most
functionality is provided at runtime through Spyce modules and Python modules.
<p>
The standard Spyce modules are documented here; some other modules are
also distributed in the contrib/ directory.
<p>
Non-implicit modules are
imported using the Spyce <a href="#lang_directive"><font
face=courier>[[.import]]</font></a> directive. Python modules are
imported using the Python <font face=courier>import</font> keyword. Remember that
modules need to have the same read permissions as regular files that you
expect the web server to read.
<p>
Modules may be imported with a non-default name using the <b>as</b> attribute
to the .import directive. This is discouraged for standard Spyce modules; the
<b>session</b> module, for example, expects to find or
otherwise load a module named <b>cookie</b> in the Spyce environment.
<p>
Once included, a Spyce module may be accessed anywhere in the Spyce code.
<big><a name="mod_db"></a><b>3.8.1. <font color=#ee0000><i>DB (implicit)</i></font></b></big><p>
Spyce integrates an advanced database module to make reading and modifying
your data faster and less repetitive.
<p>
Just initialize the <b>db</b> reference in your Spyce config file following
the examples given there, and you're all set.
<p>The general idea is, <b>db.tablename</b> represents the <b>tablename</b> table
in your database, and provides hooks for reading and modifying data in that
table in pure Python, no SQL required. We find this gives 90% of the benefits
of an object-relational mapping layer, without making developers learn another
complex tool. And since the Spyce db module is part of
<a href=http://sqlalchemy.org>SQLAlchemy</a>, probably the most advanced
database toolkit in the world, the full ORM approach is available to those
who want it.
<p>
Here's a quick example of reading
and inserting data from a table called <b>todo_lists</b>:
<table border=1 align=center>
<tr><td align=left bgcolor="#cccccc">
<font face="arial, helvetica" size="-1"><b>examples/db.spy</b></font>
</td></tr><tr><td>
<font face=courier>
<pre style='font-family: courier,monospace; font-size: small'><font color="#000000"><b><font color="#229922"><spy:parent title="To-do demo" /></font>
<font color="#0000CC">[[!
def list_new(self, api, name):
if api.db.todo_lists.selectfirst_by(name=name):
raise HandlerError('New list', 'a list with that description already exists')
api.db.todo_lists.insert(name=name)
api.db.flush()
]]</font>
(This is an self-contained example using the same database as the
<a href=/demos/to-do/index.spy>to-do demo</a>.)
<h2>To-do lists</h2>
<font color="#0000CC">[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]</font>
<font color="#229922"><spy:ul data="[L.name for L in lists]" /></font>
<h2>New list</h2>
<font color="#229922"><f:form></font>
<font color="#229922"><f:submit value="New list" handler=self.list_new /></font>:
<font color="#229922"><f:text name=name value="" /></font>
<font color="#229922"></f:form></font>
</b></font></pre>
</font>
</td></tr><tr><td align=right bgcolor="#cccccc">
<font face="arial, helvetica" size="-1">
<b><a href="/docs/examples/db.spy">Run this code</a></b>
</font>
</td></tr></table>
<p>Full SqlSoup documentation follows.
<h2>Loading objects</h2>
<p>
Loading objects is as easy as this:
<pre class=code>
>>> users = db.users.select()
>>> users.sort()
>>> users
[MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0),
MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]
</pre>
<p>
Of course, letting the database do the sort is better (".c" is short for ".columns"):
<pre class=code>
>>> db.users.select(order_by=[db.users.c.name])
[MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
</pre>
<p>
Field access is intuitive:
<pre class=code>
>>> users[0].email
u'student@example.edu'
</pre>
<p>
Of course, you don't want to load all users very often. The common case is to
select by a key or other field:
<pre class=code>
>>> db.users.selectone_by(name='Bhargan Basepair')
MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)
</pre>
<h3>Select variants</h3>
All the SqlAlchemy Query select variants are available.
Here's a quick summary of these methods:
- get(PK): load a single object identified by its primary key (either a scalar, or a tuple)
- select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by.
- select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options.
- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected.
- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected.
- count, count_by: returns an integer count of the rows selected.
See the SqlAlchemy documentation for details:
- <a href=http://www.sqlalchemy.org/docs/datamapping.myt#datamapping_query>general info and examples</a>
- <a href=http://www.sqlalchemy.org/docs/sqlconstruction.myt>details on constructing WHERE clauses</a>
<h2>Modifying objects</h2>
<p>
Modifying objects is intuitive:
<pre class=code>
>>> user = _
>>> user.email = 'basepair+nospam@example.edu'
>>> db.flush()
</pre>
<p>(SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so
multiple updates to a single object will be turned into a single UPDATE
statement when you flush.)
<p>
To finish covering the basics, let's insert a new loan, then delete it:
<pre class=code>
>>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
>>> db.flush()
>>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
>>> db.delete(loan)
>>> db.flush()
</pre>
<p>
You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more,
this time using the loans table's delete method. (For SQLAlchemy experts:
note that no flush() call is required since this
delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules
apply here as to the select methods:
<pre class=code>
>>> db.loans.insert(book_id=book_id, user_name=user.name)
MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
>>> db.flush()
>>> db.loans.delete(db.loans.c.book_id==2)
</pre>
<p>
You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2:
<pre class=code>
>>> db.loans.update(db.loans.c.book_id==2, book_id=1)
>>> db.loans.select_by(db.loans.c.book_id==1)
[MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
</pre>
<h2>Joins</h2>
<p>Occasionally, you will want to pull out a lot of data from related tables all at
once. In this situation, it is far
more efficient to have the database perform the necessary join. (Here
we do not have "a lot of data," but hopefully the concept is still clear.)
SQLAlchemy is smart enough to recognize that loans has a foreign key
to users, and uses that as the join condition automatically.
<pre class=code>
>>> join1 = db.join(db.users, db.loans, isouter=True)
>>> join1.select_by(name='Joe Student')
[MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
</pre>
<p>
You can compose arbitrarily complex joins by combining Join objects with
tables or other joins.
<pre class=code>
>>> join2 = db.join(join1, db.books)
>>> join2.select()
[MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]
</pre>
<p>
If you join tables that have an identical column name, wrap your join with "with_labels",
and all the columns will be prefixed with their table name:
<pre class=code>
>>> db.with_labels(join1).select()
[MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
users_password='student',users_classname=None,users_admin=0,
loans_book_id=1,loans_user_name='Joe Student',
loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
</pre>
<h2>Advanced usage</h2>
<p>
You can access the SqlSoup's engine attribute to compose SQL directly.
The engine's <b>execute</b> method corresponds
to the one of a DBAPI cursor, and returns a ResultProxy that has <b>fetch</b> methods
you would also see on a cursor.
<pre class=code>
>>> rp = db.engine.execute('select name, email from users order by name')
>>> for name, email in rp.fetchall(): print name, email
Bhargan Basepair basepair+nospam@example.edu
Joe Student student@example.edu
</pre>
<p>
You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details.
<p>
You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively.
<big><a name="mod_request"></a><b>3.8.2. <font color=#ee0000><i>Request (implicit)</i></font></b></big><p>
The request module is loaded implicitly into every Spyce environment.
<p>
The spyce configuration file gives two lists that affect the request module:
param_filters and file_filters. param_filters is a list of functions to run
against the GET and POST variables in the request; file filters is the same,
only for files uploaded. Each function will be passed the request module
and a dictionary when it is called; each will be called once for GET and once for POST
with each new request.
<p>
These hooks exist because the request dictionaries should not be modified
in an ad-hoc manner; these allow you to set an application-wide policy
in a well-defined manner. You might, for instance, disallow all file uploads
over 1 MB.
<p>
Here's an example that calls either Html.clean
or Html.escape (not shown) to ensure that no potentially harmful html can
be injected in user-editable areas of a site:
<table border=1 align=center>
<tr><td align=left bgcolor="#cccccc">
<font face="arial, helvetica" size="-1"><b>examples/filter.py</b></font>
</td></tr><tr><td>
<font face=courier>
<style>
.STRING {
color: #a0522d;
}
.COMMENT {
color: #ff7448;
font-family: serif;
}
.KEYWORD {
color: #9e79af;
}
.FUNCTION {
color: #0074ef;
}
</style>
<pre>
<span class="KEYWORD">def</span> <span class="FUNCTION">htmlFilter</span><span class="OP">(</span><span class="NAME">request</span><span class="OP">,</span> <span class="NAME">d</span><span class="OP">)</span><span class="OP">:</span><span class="NEWLINE">
</span> <span class="COMMENT"># note that spoofing __htmlfields doesn't help attacker get unsafe html in;
</span> <span class="COMMENT"># we always call either clean() or escape().
</span><span class="INDENT"> </span><span class="KEYWORD">try</span><span class="OP">:</span><span class="NEWLINE">
</span> <span class="COMMENT"># don't use request['__htmlfields'], or you will recurse infinitely
</span><span class="INDENT"> </span><span class="NAME">toClean</span> <span class="OP">=</span> <span class="NAME">request</span><span class="OP">.</span><span class="NAME">_post</span><span class="OP">[</span><span class="STRING">'__htmlfields'</span><span class="OP">]</span><span class="OP">[</span><span class="NUMBER">0</span><span class="OP">]</span><span class="OP">.</span><span class="NAME">split</span><span class="OP">(</span><span class="STRING">','</span><span class="OP">)</span><span class="NEWLINE">
</span> <span class="DEDENT"></span><span class="KEYWORD">except</span> <span class="NAME">KeyError</span><span class="OP">:</span><span class="NEWLINE">
</span><span class="INDENT"> </span><span class="NAME">toClean</span> <span class="OP">=</span> <span class="OP">[</span><span class="OP">]</span><span class="NEWLINE">
</span> <span class="DEDENT"></span><span class="KEYWORD">for</span> <span class="NAME">key</span> <span class="KEYWORD">in</span> <span class="NAME">d</span><span class="OP">:</span><span class="NEWLINE">