Skip to content
Browse files

Splunk Python SDK 0.1.0 Release

  • Loading branch information...
0 parents commit f5775756a706d7afea8fd9eb4c8378de12a59afa @bradlo bradlo committed with itay Mar 20, 2011
Showing with 346,715 additions and 0 deletions.
  1. +13 −0 .gitignore
  2. +235 −0 .pylintrc
  3. +5 −0 CHANGELOG.md
  4. +202 −0 LICENSE
  5. +1 −0 MANIFEST.in
  6. +229 −0 README.md
  7. +141 −0 docs/endpoints.csv
  8. +24 −0 examples/abc/README.md
  9. +68 −0 examples/abc/a.py
  10. +41 −0 examples/abc/b.py
  11. +31 −0 examples/abc/c.py
  12. +152 −0 examples/analytics/README.md
  13. +18 −0 examples/analytics/__init__.py
  14. +2,526 −0 examples/analytics/bottle.py
  15. +279 −0 examples/analytics/css/analytics.css
  16. +30 −0 examples/analytics/css/jquery.ui.selectmenu.css
  17. +13 −0 examples/analytics/css/showLoading.css
  18. BIN examples/analytics/images/loading.gif
  19. +97 −0 examples/analytics/input.py
  20. +125 −0 examples/analytics/js/date.format.js
  21. +2,599 −0 examples/analytics/js/jquery.flot.js
  22. +344 −0 examples/analytics/js/jquery.flot.selection.js
  23. +250 −0 examples/analytics/js/jquery.showLoading.js
  24. +802 −0 examples/analytics/js/jquery.ui.selectmenu.js
  25. +168 −0 examples/analytics/output.py
  26. +153 −0 examples/analytics/server.py
  27. +396 −0 examples/analytics/templates/application.tpl
  28. +52 −0 examples/analytics/templates/applications.tpl
  29. +11 −0 examples/analytics/templates/make_table.tpl
  30. +50 −0 examples/async/README.md
  31. +194 −0 examples/async/async.py
  32. +56 −0 examples/binding1.py
  33. +163 −0 examples/conf.py
  34. +137 −0 examples/custom_search/README.md
  35. +185 −0 examples/custom_search/bin/usercount.py
  36. +13 −0 examples/custom_search/default/app.conf
  37. +18 −0 examples/custom_search/default/data/ui/nav/default.xml
  38. +1 −0 examples/custom_search/default/data/ui/views/README
  39. +3 −0 examples/custom_search/local/app.conf
  40. +7 −0 examples/custom_search/local/commands.conf
  41. +29 −0 examples/custom_search/metadata/default.meta
  42. +7 −0 examples/custom_search/metadata/local.meta
  43. +28 −0 examples/dashboard/README.md
  44. +202 −0 examples/dashboard/feed.py
  45. +29 −0 examples/explorer/README.md
  46. +18,516 −0 examples/explorer/endpoints.js
  47. +189 −0 examples/explorer/explorer.css
  48. +524 −0 examples/explorer/explorer.html
  49. +68 −0 examples/explorer/explorer.py
  50. +2 −0 examples/explorer/prettify/lang-apollo.js
  51. +18 −0 examples/explorer/prettify/lang-clj.js
  52. +2 −0 examples/explorer/prettify/lang-css.js
  53. +1 −0 examples/explorer/prettify/lang-go.js
  54. +2 −0 examples/explorer/prettify/lang-hs.js
  55. +3 −0 examples/explorer/prettify/lang-lisp.js
  56. +2 −0 examples/explorer/prettify/lang-lua.js
  57. +2 −0 examples/explorer/prettify/lang-ml.js
  58. +4 −0 examples/explorer/prettify/lang-n.js
  59. +1 −0 examples/explorer/prettify/lang-proto.js
  60. +2 −0 examples/explorer/prettify/lang-scala.js
  61. +2 −0 examples/explorer/prettify/lang-sql.js
  62. +1 −0 examples/explorer/prettify/lang-tex.js
  63. +2 −0 examples/explorer/prettify/lang-vb.js
  64. +3 −0 examples/explorer/prettify/lang-vhdl.js
  65. +2 −0 examples/explorer/prettify/lang-wiki.js
  66. +3 −0 examples/explorer/prettify/lang-xq.js
  67. +2 −0 examples/explorer/prettify/lang-yaml.js
  68. +1 −0 examples/explorer/prettify/prettify.css
  69. +28 −0 examples/explorer/prettify/prettify.js
  70. +162 −0 examples/explorer/server.py
  71. +56 −0 examples/export/README.md
  72. +544 −0 examples/export/export.py
  73. +88 −0 examples/follow.py
  74. +120 −0 examples/genevents.py
  75. +23 −0 examples/handlers/README.md
  76. +16 −0 examples/handlers/cacert.bad.pem
  77. +16 −0 examples/handlers/cacert.pem
  78. +110 −0 examples/handlers/handler_certs.py
  79. +42 −0 examples/handlers/handler_debug.py
  80. +83 −0 examples/handlers/handler_proxy.py
  81. +47 −0 examples/handlers/handler_urllib2.py
  82. +341 −0 examples/handlers/tiny-proxy.py
  83. +191 −0 examples/index.py
  84. +42 −0 examples/info.py
  85. +39 −0 examples/inputs.py
  86. +271 −0 examples/job.py
  87. +36 −0 examples/loggers.py
  88. +46 −0 examples/odata/README.md
  89. +721 −0 examples/odata/excel_proxy.py
  90. +53 −0 examples/oneshot.py
  91. +70 −0 examples/results.py
  92. +127 −0 examples/saved_search/README.md
  93. +210 −0 examples/saved_search/saved_search.py
  94. +111 −0 examples/search.py
  95. +133 −0 examples/spcmd.py
  96. +49 −0 examples/spurl.py
  97. +59 −0 examples/stail.py
  98. +79 −0 examples/submit.py
  99. +31 −0 examples/twitted/README.md
  100. +4 −0 examples/twitted/clean
  101. +278 −0 examples/twitted/input.py
  102. +6 −0 examples/twitted/reload
  103. +4 −0 examples/twitted/run
  104. +4 −0 examples/twitted/search
  105. +182 −0 examples/twitted/twitted/bin/hashtags.py
  106. +187 −0 examples/twitted/twitted/bin/tophashtags.py
  107. +13 −0 examples/twitted/twitted/default/app.conf
  108. +18 −0 examples/twitted/twitted/default/data/ui/nav/default.xml
  109. +4 −0 examples/twitted/twitted/local/app.conf
  110. +15 −0 examples/twitted/twitted/local/commands.conf
  111. +4 −0 examples/twitted/twitted/local/indexes.conf
  112. +8 −0 examples/twitted/twitted/local/inputs.conf
  113. +6 −0 examples/twitted/twitted/local/props.conf
  114. +135 −0 examples/twitted/twitted/local/savedsearches.conf
  115. +14 −0 examples/twitted/twitted/local/transforms.conf
  116. +219 −0 examples/twitted/twitted/local/viewstates.conf
  117. +29 −0 examples/twitted/twitted/metadata/default.meta
  118. +139 −0 examples/twitted/twitted/metadata/local.meta
  119. +78 −0 examples/upload.py
  120. +49 −0 setup.py
  121. +23 −0 sitecustomize.py
  122. +19 −0 splunk/__init__.py
  123. +286 −0 splunk/binding.py
  124. +608 −0 splunk/client.py
  125. +185 −0 splunk/data.py
  126. +401 −0 splunk/results.py
  127. +17 −0 splunkrc.spec
  128. +36 −0 tests/.coveragerc
  129. +46 −0 tests/README.md
  130. +8 −0 tests/custom_search/hashtags.baseline
  131. +14 −0 tests/custom_search/hashtags.in
  132. +4 −0 tests/custom_search/tophashtags.baseline
  133. +18 −0 tests/custom_search/tophashtags.in
  134. +3 −0 tests/custom_search/usercount.baseline
  135. +272 −0 tests/custom_search/usercount.in
  136. +200,057 −0 tests/results.xml
  137. +54,038 −0 tests/results200.xml
  138. +54,038 −0 tests/results404.xml
  139. +34 −0 tests/runtests.py
  140. +43 −0 tests/services.server.info.xml
  141. +111 −0 tests/services.xml
  142. +1 −0 tests/splunk.baseline
  143. +1 −0 tests/splunk.binding.baseline
  144. +2 −0 tests/splunk.client.baseline
  145. +1 −0 tests/splunk.data.baseline
  146. +1 −0 tests/splunk.results.baseline
  147. +365 −0 tests/test_binding.py
  148. +492 −0 tests/test_client.py
  149. +197 −0 tests/test_data.py
  150. +364 −0 tests/test_examples.py
  151. +1 −0 tests/testfile.txt
  152. +97 −0 utils/__init__.py
  153. +113 −0 utils/cmdopts.py
13 .gitignore
@@ -0,0 +1,13 @@
+*.pyc
+*.swp
+*.idea
+*.DS_Store*
+*coverage_html_report*
+.coverage.*
+proxypid
+proxy.log
+__stdout__
+.coverage
+MANIFEST
+build/*
+dist/*
235 .pylintrc
@@ -0,0 +1,235 @@
+[MASTER]
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+disable=E1103,C0111,C0321,R0903,R0904,W0122,W0142,W0703
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Include message's id in output
+include-ids=yes
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{0,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO,UNDONE
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
5 CHANGELOG.md
@@ -0,0 +1,5 @@
+# Splunk Python SDK Changelog
+
+## v0.1.0
+
+* Initial Python SDK release
202 LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
1 MANIFEST.in
@@ -0,0 +1 @@
+include README.md
229 README.md
@@ -0,0 +1,229 @@
+# The Splunk Software Development Kit for Python (Preview Release)
+
+This SDK contains library code and examples designed to enable developers to
+build applications using Splunk.
+
+Splunk is a search engine and analytic environment that uses a distributed
+map-reduce architecture to efficiently index, search and process large
+time-varying data sets.
+
+The Splunk product is popular with system administrators for aggregation and
+monitoring of IT machine data, security, compliance and a wide variety of other
+scenarios that share a requirement to efficiently index, search, analyze and
+generate real-time notifications from large volumes of time series data.
+
+The Splunk developer platform enables developers to take advantage of the same
+technology used by the Splunk product to build exciting new applications that
+are enabled by Splunk's unique capabilities.
+
+
+## License
+
+The Splunk Software Development Kit for Python is licensed under the Apache
+License 2.0. Details can be found in the file LICENSE.
+
+## This SDK is a Preview Release
+
+1. This Preview release a pre-beta release. There will also be a beta release
+ prior to a general release. It is incomplete and may have bugs.
+
+2. The Apache license only applies to the SDK and no other Software provided
+ by Splunk.
+
+3. Splunk in using the Apache license is not providing any warranties,
+ indemnification or accepting any liabilities with the Preview SDK.
+
+4. Splunk is not accepting any Contributions to the Preview release of the SDK.
+ All Contributions during the Preview SDK will be returned without review.
+
+## Getting Started
+
+In order to use the SDK you are going to need a copy of Splunk. If you don't
+already have a copy you can download one from http://www.splunk.com/download.
+
+You can get a copy of the SDK sources by cloning into the repository with git:
+
+ git clone https://github.com/splunk/splunk-sdk-python.git
+
+#### Installing
+
+You can install the Splunk SDK libraries by using `easy_install` or `pip`:
+
+ [sudo] easy_install splunk-sdk
+
+Or
+
+ [sudo] pip install splunk-sdk
+
+Alternatively, you can use `setup.py` on the sources you cloned from GitHub:
+
+ [sudo] python setup.py install
+
+However, it's not necessarry to install the libraries in order to run the
+examples and unit tests from the SDK.
+
+#### Requirements
+
+The SDK requires Python 2.6+.
+
+#### Running the examples and units
+
+In order to run the Splunk examples and unit tests, you must put the root of
+the SDK on your PYTHONPATH.
+
+The SDK command line examples require a common set of command line arguments
+that specify things like the Splunk host and port and login credentials. You
+can get a full list of command line arguments by typing `--help` as an argument
+to any of the command line examples.
+
+#### .splunkrc
+
+The examples and units are also desigend to receive arguments from an optional
+`.splunkrc` file located in your home directory. The format of the file is
+simply a list of key=value pairs, same as the options that are taken on the
+command line, for example:
+
+ host=localhost
+ username=admin
+ password=changeme
+
+The `.splunkrc` file is a feature of the SDK examples and unit tests and not
+the Splunk platform or SDK libraries and is indended simply as convenience for
+developers using the SDK.
+
+The `.splunkrc` file should not be used for storing user credentials for apps
+built on Splunk and should not be used if you are concerned about the security
+of the credentails used in your development environment.
+
+You can view a sample `.splunkrc` file by looking at the `splunkrc.spec` file
+in the root directory of the repistory.
+
+## Overview
+
+The Splunk developer platform consists of three primary components: `splunkd`,
+the engine, `splunkweb`, the app framework that sits on top of the engine,
+and the Splunk SDKs that interface with the REST API.
+
+This SDK enables developers to target `splunkd` by making calls against the
+engine's REST API and by accessing the various `splunkd` extension points such
+as custom search commands, lookup functions, scripted inputs and custom REST
+handlers.
+
+You can find additional information about building applications on Splunk at
+our developer portal at COMING SOON.
+
+### Hello Splunk
+
+The Splunk library included in this SDK consists of two layers of API that
+can be used to interact with splunkd. The lower layer is referred to as the
+_binding_ layer. It is a thin wrapper around low-level HTTP capabilities,
+including:
+
+* A pluggable HTTP component that can be user-supplied.
+* Handles authentication and namespace URL management
+* Accessible low-level HTTP interface for use by developers who want
+ to be close to the wire.
+
+You can see an example use of the library here:
+
+ import splunk.binding as binding
+
+ # host defaults to localhost and port defaults to 8089
+ context = binding.connect(username="admin", password="changeme")
+
+ response = context.get('/services/authentication/users')
+
+ print "Status: %s" % response.status
+ print response.body.read()
+
+The second layer is referred to as the _client_ layer and builds on the
+_binding_ layer to provide a friendlier interface to Splunk that abstracts
+away some of the lower level details of the _binding_ layer.
+
+ from pprint import pprint
+
+ import splunk.client as client
+
+ # host defaults to localhost and port defaults to 8089
+ service = client.connect(username="admin", password="changeme")
+
+ for user in service.users:
+ pprint(user())
+
+### Unit tests
+
+The SDK contains a small but growing collection of unit tests. Running the
+tests is simple and rewarding:
+
+ cd tests
+ ./runtests.py
+
+Alternatively, you can read more about our testing "framework"
+[here](https://github.com/splunk/splunk-sdk-python/tree/master/tests).
+
+### Layout of the repository
+
+<dl>
+<dt>./docs</dt>
+<dd>Contains a few detailed notes specific to the SDK. In general documentation
+ about developing on Splunk can be found on dev.splunk.com.</dd>
+<dt>./examples</dt>
+<dd>Contains s variety of Splunk samples demonstrating the various library
+ modules.</dd>
+<dt>./splunk</dt>
+<dd>The Splunk library modules.</dd>
+<dt>./tests</dt>
+<dd>The SDK unit tests.</dd>
+<dt>./utils</dt>
+<dd>Generic utility code shared by the examples and unit tests.</dd>
+</dl>
+
+### Changelog
+
+You can look at the changelog for each version
+[here](https://github.com/splunk/splunk-sdk-python/blob/master/CHANGELOG.md)
+
+### Branches
+
+The `master` branch always represents a stable and released version of the SDK.
+You can read more about our branching model on our
+[Wiki](https://github.com/splunk/splunk-sdk-python/wiki/Branching-Model).
+
+## Resources
+
+You can find anything having to do with developing on Splunk at the Splunk
+developer portal:
+
+* **COMING SOON**
+
+You can also find full reference documentation of the REST API:
+
+* http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI
+
+For a gentle introduction to the Splunk product and some of its capabilities:
+
+* **COMING SOON**
+
+## Community
+
+* Email: Stay connected with other developers building on Splunk: **COMING SOON**
+* Issues: https://github.com/splunk/splunk-sdk-python/issues
+* Answers: Check out this tag on Splunk answers for:
+ http://splunk-base.splunk.com/tags/python/
+* Blog: http://blogs.splunk.com/dev/
+* Twitter: **COMING SOON**
+
+### How to contribute
+
+We aren't ready to accept code contributions yet, but will be shortly. Check
+this README for more updates soon.
+
+### Support
+
+* SDKs in Preview will not be Splunk supported. Once the Python SDK moves to
+an Open Beta we will provide more detail on support.
+
+* Issues should be filed here: https://github.com/splunk/splunk-sdk-python/issues
+
+### Contact Us
+You can reach the Dev Platform team at devinfo@splunk.com
141 docs/endpoints.csv
@@ -0,0 +1,141 @@
+|auth/login
+alerts/fired_alerts
+alerts/fired_alerts/{-}
+apps/appinstall
+apps/apptemplates
+apps/apptemplates/{name}
+apps/local
+apps/local/{name}
+apps/local/{name}/package
+#apps/remote
+authentication/auth-tokens
+authentication/auth-tokens/{?}
+authentication/changepassword
+authentication/changepassword/{name}
+authentication/current-context
+authentication/current-context/context
+authentication/httpauth-tokens
+authentication/httpauth-tokens/{key}
+authentication/roles
+authentication/roles/{name}
+authentication/users
+authentication/users/{name}
+authorization/capabilities
+authorization/capabilities/{name}
+authorization/roles
+authorization/roles/{name}
+configs/deploymentclient
+configs/inputs
+configs/inputs/{name}
+configs/wmi
+data/commands
+data/commands/{name}
+data/indexes
+data/indexes/{name}
+data/inputs/monitor
+data/inputs/monitor/{name}
+data/inputs/monitor/{name}/members
+data/inputs/monitor/{name}/members/{name}
+data/inputs/oneshot
+data/inputs/script
+data/inputs/script/restart
+data/inputs/tcp/cooked
+data/inputs/tcp/cooked/{name}
+data/inputs/tcp/raw
+data/inputs/tcp/raw/{name}
+data/inputs/tcp/ssl
+data/inputs/udp
+data/lookup-table-files
+data/lookup-table-files/{name}
+data/outputs/tcp/default
+data/outputs/tcp/default/{name}
+data/outputs/tcp/group
+data/outputs/tcp/group/{name}
+data/outputs/tcp/server
+data/outputs/tcp/server/{name}
+data/outputs/tcp/syslog
+data/outputs/tcp/syslog/{name}
+data/props/extractions
+data/props/extractions/{name}
+data/props/fieldaliases
+data/props/fieldaliases/{name}
+data/props/lookups
+data/props/lookups/{name}
+data/props/sourcetype-rename
+data/props/sourcetype-rename/{name}
+data/transforms/extractions
+data/transforms/extractions/{name}
+data/transforms/lookups
+data/transforms/lookups/{name}
+#data/ui/...
+#data/user-prefs/...
+deployment/client
+deployment/server
+deployment/server/default
+deployment/serverclass
+deployment/serverclass/{name}
+deployment/serverclass_status
+deployment/serverclass_status/{name}
+deployment/tenants
+deployment/tenants/default
+directory
+directory/{name}
+licenser/groups
+licenser/groups/{name}
+licenser/licenses
+licenser/licenses/{hash}
+licenser/messages
+licenser/pools
+licenser/pools/{name}
+licenser/slaves
+licenser/slaves/{guid}
+licenser/stacks
+licenser/stacks/{name}
+messages
+|properties
+|properties/{file}
+|properties/{file}/{stanza}
+|properties/{file}/{stanza}/{key}
+receivers/simple
+receivers/stream
+saved/eventtypes
+saved/eventtypes/{name}
+saved/searches
+saved/searches/{name}
+saved/searches/{name}/acknowledge
+saved/searches/{name}/disable
+saved/searches/{name}/dispatch
+saved/searches/{name}/history
+saved/searches/{name}/notify
+saved/searches/{name}/scheduled_times
+saved/searches/{name}/suppress
+scheduled/views
+#search/commands
+#search/commands/{name}
+search/distributed/config
+search/distributed/peers
+|search/fields
+|search/fields/{name}
+|search/fields/{name}/tags
+|search/jobs
+|search/jobs/{sid}/control
+|search/jobs/{sid}/events
+|search/jobs/{sid}/results
+|search/jobs/{sid}/results_preview
+|search/jobs/{sid}/search.log
+|search/jobs/{sid}/timeline
+|search/jobs/{sid}/summary
+|search/jobs/export
+|search/parser
+|search/tags
+|search/tags/{name}
+|search/timeparser
+|search/typeahead
+server/control
+server/info
+server/info/server-info
+server/logger
+server/logger/{name}
+server/settings
+storage/passwords
+storage/passwords/{name}
24 examples/abc/README.md
@@ -0,0 +1,24 @@
+# The ABCs of Calling the Splunk REST API
+
+This example shows three different approaches to making calls against the
+Splunk REST API.
+
+The examples all happen to retrieve a list of installed apps from a given
+Splunk instance, but they could apply as easily to any other area of the REST
+API.
+
+* **a.py** uses Python's standard httplib module to make calls against the
+ Splunk REST API. This example does not use any SDK libraries to access
+ Splunk.
+
+* **b.py** users the SDK's lower level binding module to access the REST API.
+ The binding module handles authentication details (and some additional book-
+ keeping details not demonstrated by this sample) and the result is a much
+ simplified interaction with Splunk, but its still very much a 'wire' level
+ coding experience.
+
+* **c.py** uses the SDK client module, which abstracts away most most of the
+ wire level details of invoking the REST API, but that still presents a
+ stateless interface to Splunk the attempts to faithfully represent the
+ semantics of the underlying REST API.
+
68 examples/abc/a.py
@@ -0,0 +1,68 @@
+# Copyright 2011 Splunk, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"): you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Retrieves a list of installed apps from Splunk by making REST API calls
+ using Python's httplib module."""
+
+import httplib
+import urllib
+from xml.etree import ElementTree
+
+HOST = "localhost"
+PORT = 8089
+USERNAME = "admin"
+PASSWORD = "changeme"
+
+# Present credentials to Splunk and retrieve the session key
+connection = httplib.HTTPSConnection(HOST, PORT)
+body = urllib.urlencode({'username': USERNAME, 'password': PASSWORD})
+headers = {
+ 'Content-Type': "application/x-www-form-urlencoded",
+ 'Content-Length': str(len(body)),
+ 'Host': HOST,
+ 'User-Agent': "a.py/1.0",
+ 'Accept': "*/*"
+}
+try:
+ connection.request("POST", "/services/auth/login", body, headers)
+ response = connection.getresponse()
+finally:
+ connection.close()
+if response.status != 200:
+ raise Exception, "%d (%s)" % (response.status, response.reason)
+body = response.read()
+sessionKey = ElementTree.XML(body).findtext("./sessionKey")
+
+# Now make the request to Splunk for list of installed apps
+connection = httplib.HTTPSConnection(HOST, PORT)
+headers = {
+ 'Content-Length': "0",
+ 'Host': HOST,
+ 'User-Agent': "a.py/1.0",
+ 'Accept': "*/*",
+ 'Authorization': "Splunk %s" % sessionKey,
+}
+try:
+ connection.request("GET", "/services/apps/local", "", headers)
+ response = connection.getresponse()
+finally:
+ connection.close()
+if response.status != 200:
+ raise Exception, "%d (%s)" % (response.status, response.reason)
+
+body = response.read()
+data = ElementTree.XML(body)
+apps = data.findall("{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}title")
+for app in apps:
+ print app.text
41 examples/abc/b.py
@@ -0,0 +1,41 @@
+# Copyright 2011 Splunk, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"): you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Retrieves a list of installed apps from Splunk using the binding module."""
+
+from xml.etree import ElementTree
+
+import splunk.binding as binding
+
+HOST = "localhost"
+PORT = 8089
+USERNAME = "admin"
+PASSWORD = "changeme"
+
+context = binding.connect(
+ host=HOST,
+ port=PORT,
+ username=USERNAME,
+ password=PASSWORD)
+
+response = context.get('apps/local')
+if response.status != 200:
+ raise Exception, "%d (%s)" % (response.status, response.reason)
+
+body = response.body.read()
+data = ElementTree.XML(body)
+apps = data.findall("{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}title")
+for app in apps:
+ print app.text
+
31 examples/abc/c.py
@@ -0,0 +1,31 @@
+# Copyright 2011 Splunk, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"): you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Retrieves a list of installed apps from Splunk using the client module."""
+
+import splunk.client as client
+
+HOST = "localhost"
+PORT = 8089
+USERNAME = "admin"
+PASSWORD = "changeme"
+
+service = client.connect(
+ host=HOST,
+ port=PORT,
+ username=USERNAME,
+ password=PASSWORD)
+
+for app in service.apps:
+ print app.name
152 examples/analytics/README.md
@@ -0,0 +1,152 @@
+# Analytics Example
+
+The Analytics example is meant as a sample implementation of a
+"mini-Google Analytics" or "mini-Mixpanel" style web service.
+
+At its core, it allows for logging of arbitrary events together with arbitrary
+`key=value` properties for each event. You don't need to define a schema
+up front and some events can have more properties than others (even within
+the same kind of event).
+
+This type of service is especially suited to Splunk, given the temporal nature
+of the data, together with the lack of schema and no need to update past events.
+
+## Architecture
+
+The main component of the Analytics example are two pieces of reusable code
+meant to manage input and output of data into Splunk.
+
+### AnalyticsTracker
+
+The `input.py` file defines the "input" side of the Analytics service. If you
+wanted to log some analytics data in your app, you would the `AnalyticsTracker`
+class defined in this file in order to do so.
+
+The `AnalyticsTracker` class encapsulates all the information required to log
+events to Splunk. This includes the "application" name (think of it as a sort
+of namespace, if you wanted to log multiple apps' worth of events into the
+same Splunk instance) and Splunk connection parameters. It also takes
+an optional "index" parameter, but that's there mostly for testing purposes.
+
+So, for example, you could write an `AnalyticsTracker` like this:
+
+```python
+from analytics.input import AnalyticsTracker
+
+splunk_opts = ...
+tracker = AnalyticsTracker("myapp", splunk_opts)
+```
+
+Once you have an instance of the `AnalyticsTracker`, you can use it to track
+your events. For example, if you wanted to log an event regarding a user
+logging in, and you wanted to add the name of the user and also his user
+agent, you could do something like this:
+
+```python
+userid = ...
+username = ...
+useragent = ...
+tracker.track("login", distinct_id = user_id, "username"=username, "useragent"=useragent)
+```
+
+The first parameter is the name of the event you want to log. The `distinct_id`
+parameter specifies a "unique ID". You can use the unique ID to group events,
+for example if you only wanted to count unique logins by user_id. The rest of
+the parameters are arbitrary `key=value` pairs that you can also extract.
+
+Internally, when you ask the `AnalyticsTracker` to log an event, it will construct
+a textual representation of that event. It will also make sure to encode all the
+content to fit properly in Splunk. For example, for the above event, it
+would look something like this:
+
+```
+2011-08-08T11:45:17.735045 application="myapp" event="login" distinct_id="..." analytics_prop__username="..." analytics_prop__useragent="..."
+```
+
+The reason that we use the `analytics_prop__` prefix is to make sure there is
+no ambiguity between known fields such as `application` and `event` and user
+supplied `key=value=` properties.
+
+### AnalyticsRetriever
+
+Similarly to `AnalyticsTracker`, the `output.py` file defines the "output" side
+of the Analytics service. If you want to extract the events you logged in using
+`AnalyticsTracker`, you'd use the `AnalyticsRetriever` class.
+
+Creating an `AnalyticsTracker` instance is identical to the `AnalyticsTracker`:
+
+```python
+from analytics.output import AnalyticsRetriever
+
+splunk_opts = ...
+retriever = AnalyticsRetriever("myapp", splunk_opts)
+```
+
+Once you have an instance of the `AnalyticsRetriever`, you can use its variety
+of methods in order to query information about events.
+
+Executing each of the methods will execute a Splunk search, retrieve the
+results, and transform them into a well-defined Python dictionary format.
+
+#### Examples
+
+Listing all applications:
+
+```python
+print retriever.applications()
+```
+
+Listing all the types of events in the system:
+
+```python
+print retriever.events()
+```
+
+Listing all the union of all the properties used for a particular event:
+
+```python
+event_name = "login"
+print retriever.properties(event_name)
+```
+
+Getting all the values of a given property for some event:
+
+```python
+event_name = "login"
+prop_name = "useragent"
+print retriever.property_values(event_name, prop_name))
+```
+
+Getting a "graph" of event information over time for all events of a
+specific application (this uses the default TimeRange.MONTH):
+
+```python
+print retriever.events_over_time()
+```
+
+Getting a graph of event information over time for a specific event:
+
+```python
+print retriever.events_over_time(event_name="login")
+```
+
+### server.py
+
+The `server.py` file provides a sample "web app" built on top of the
+Analytics service. It lists applications, and for each application
+you can see a graph of events over time, properties, etc.
+
+We make use of the excellent open source
+[flot](http://code.google.com/p/flot/) graphing library to render
+our Javascript graphs. We also use the [`bottle.py`](bottlepy.org)
+micro-web framework.
+
+## Running the Sample
+
+In order to run the sample, you can simply execute:
+
+ ./server.py
+
+And navigate to http://localhost:8080. I suggest you input some
+events in beforehand, though `server.py` logs some events itself
+as you navigate the site (it's meta analytics!).
18 examples/analytics/__init__.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Splunk, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"): you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import input
+import output
2,526 examples/analytics/bottle.py
@@ -0,0 +1,2526 @@
+# -*- coding: utf-8 -*-
+"""
+Bottle is a fast and simple micro-framework for small web applications. It
+offers request dispatching (Routes) with url parameter support, templates,
+a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
+template engines - all in a single file and with no dependencies other than the
+Python Standard Library.
+
+Homepage and documentation: http://bottlepy.org/
+
+Copyright (c) 2011, Marcel Hellkamp.
+License: MIT (see LICENSE.txt for details)
+"""
+
+from __future__ import with_statement
+
+__author__ = 'Marcel Hellkamp'
+__version__ = '0.9.6'
+__license__ = 'MIT'
+
+import base64
+import cgi
+import email.utils
+import functools
+import hmac
+import httplib
+import imp
+import itertools
+import mimetypes
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import thread
+import threading
+import time
+import warnings
+
+from Cookie import SimpleCookie
+from tempfile import TemporaryFile
+from traceback import format_exc
+from urllib import urlencode, quote as urlquote, unquote as urlunquote
+from urlparse import urlunsplit, urljoin, SplitResult as UrlSplitResult
+
+try: from collections import MutableMapping as DictMixin
+except ImportError: # pragma: no cover
+ from UserDict import DictMixin
+
+try: from urlparse import parse_qs
+except ImportError: # pragma: no cover
+ from cgi import parse_qs
+
+try: import cPickle as pickle
+except ImportError: # pragma: no cover
+ import pickle
+
+try: from json import dumps as json_dumps
+except ImportError: # pragma: no cover
+ try: from simplejson import dumps as json_dumps
+ except ImportError: # pragma: no cover
+ try: from django.utils.simplejson import dumps as json_dumps
+ except ImportError: # pragma: no cover
+ json_dumps = None
+
+NCTextIOWrapper = None
+if sys.version_info >= (3,0,0): # pragma: no cover
+ # See Request.POST
+ from io import BytesIO
+ def touni(x, enc='utf8', err='strict'):
+ """ Convert anything to unicode """
+ return str(x, enc, err) if isinstance(x, bytes) else str(x)
+ if sys.version_info < (3,2,0):
+ from io import TextIOWrapper
+ class NCTextIOWrapper(TextIOWrapper):
+ ''' Garbage collecting an io.TextIOWrapper(buffer) instance closes
+ the wrapped buffer. This subclass keeps it open. '''
+ def close(self): pass
+else:
+ from StringIO import StringIO as BytesIO
+ bytes = str
+ def touni(x, enc='utf8', err='strict'):
+ """ Convert anything to unicode """
+ return x if isinstance(x, unicode) else unicode(str(x), enc, err)
+
+def tob(data, enc='utf8'):
+ """ Convert anything to bytes """
+ return data.encode(enc) if isinstance(data, unicode) else bytes(data)
+
+# Convert strings and unicode to native strings
+if sys.version_info >= (3,0,0):
+ tonat = touni
+else:
+ tonat = tob
+tonat.__doc__ = """ Convert anything to native strings """
+
+
+# Backward compatibility
+def depr(message, critical=False):
+ if critical: raise DeprecationWarning(message)
+ warnings.warn(message, DeprecationWarning, stacklevel=3)
+
+
+# Small helpers
+def makelist(data):
+ if isinstance(data, (tuple, list, set, dict)): return list(data)
+ elif data: return [data]
+ else: return []
+
+
+class DictProperty(object):
+ ''' Property that maps to a key in a local dict-like attribute. '''
+ def __init__(self, attr, key=None, read_only=False):
+ self.attr, self.key, self.read_only = attr, key, read_only
+
+ def __call__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter, self.key = func, self.key or func.__name__
+ return self
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ key, storage = self.key, getattr(obj, self.attr)
+ if key not in storage: storage[key] = self.getter(obj)
+ return storage[key]
+
+ def __set__(self, obj, value):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ getattr(obj, self.attr)[self.key] = value
+
+ def __delete__(self, obj):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ del getattr(obj, self.attr)[self.key]
+
+def cached_property(func):
+ ''' A property that, if accessed, replaces itself with the computed
+ value. Subsequent accesses won't call the getter again. '''
+ return DictProperty('__dict__')(func)
+
+class lazy_attribute(object): # Does not need configuration -> lower-case name
+ ''' A property that caches itself to the class object. '''
+ def __init__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter = func
+
+ def __get__(self, obj, cls):
+ value = self.getter(cls)
+ setattr(cls, self.__name__, value)
+ return value
+
+
+
+
+
+
+###############################################################################
+# Exceptions and Events ########################################################
+###############################################################################
+
+
+class BottleException(Exception):
+ """ A base class for exceptions used by bottle. """
+ pass
+
+
+class HTTPResponse(BottleException):
+ """ Used to break execution and immediately finish the response """
+ def __init__(self, output='', status=200, header=None):
+ super(BottleException, self).__init__("HTTP Response %d" % status)
+ self.status = int(status)
+ self.output = output
+ self.headers = HeaderDict(header) if header else None
+
+ def apply(self, response):
+ if self.headers:
+ for key, value in self.headers.iterallitems():
+ response.headers[key] = value
+ response.status = self.status
+
+
+class HTTPError(HTTPResponse):
+ """ Used to generate an error page """
+ def __init__(self, code=500, output='Unknown Error', exception=None,
+ traceback=None, header=None):
+ super(HTTPError, self).__init__(output, code, header)
+ self.exception = exception
+ self.traceback = traceback
+
+ def __repr__(self):
+ return template(ERROR_PAGE_TEMPLATE, e=self)
+
+
+
+
+
+
+###############################################################################
+# Routing ######################################################################
+###############################################################################
+
+
+class RouteError(BottleException):
+ """ This is a base class for all routing related exceptions """
+
+
+class RouteReset(BottleException):
+ """ If raised by a plugin or request handler, the route is reset and all
+ plugins are re-applied. """
+
+
+class RouteSyntaxError(RouteError):
+ """ The route parser found something not supported by this router """
+
+
+class RouteBuildError(RouteError):
+ """ The route could not been built """
+
+
+class Router(object):
+ ''' A Router is an ordered collection of route->target pairs. It is used to
+ efficiently match WSGI requests against a number of routes and return
+ the first target that satisfies the request. The target may be anything,
+ usually a string, ID or callable object. A route consists of a path-rule
+ and a HTTP method.
+
+ The path-rule is either a static path (e.g. `/contact`) or a dynamic
+ path that contains wildcards (e.g. `/wiki/:page`). By default, wildcards
+ consume characters up to the next slash (`/`). To change that, you may
+ add a regular expression pattern (e.g. `/wiki/:page#[a-z]+#`).
+
+ For performance reasons, static routes (rules without wildcards) are
+ checked first. Dynamic routes are searched in order. Try to avoid
+ ambiguous or overlapping rules.
+
+ The HTTP method string matches only on equality, with two exceptions:
+ * ´GET´ routes also match ´HEAD´ requests if there is no appropriate
+ ´HEAD´ route installed.
+ * ´ANY´ routes do match if there is no other suitable route installed.
+
+ An optional ``name`` parameter is used by :meth:`build` to identify
+ routes.
+ '''
+
+ default = '[^/]+'
+
+ @lazy_attribute
+ def syntax(cls):
+ return re.compile(r'(?<!\\):([a-zA-Z_][a-zA-Z_0-9]*)?(?:#(.*?)#)?')
+
+ def __init__(self):
+ self.routes = {} # A {rule: {method: target}} mapping
+ self.rules = [] # An ordered list of rules
+ self.named = {} # A name->(rule, build_info) mapping
+ self.static = {} # Cache for static routes: {path: {method: target}}
+ self.dynamic = [] # Cache for dynamic routes. See _compile()
+
+ def add(self, rule, method, target, name=None, static=False):
+ ''' Add a new route or replace the target for an existing route. '''
+ if static:
+ depr("Use a backslash to escape ':' in routes.") # 0.9
+ rule = rule.replace(':','\\:')
+
+ if rule in self.routes:
+ self.routes[rule][method.upper()] = target
+ else:
+ self.routes[rule] = {method.upper(): target}
+ self.rules.append(rule)
+ if self.static or self.dynamic: # Clear precompiler cache.
+ self.static, self.dynamic = {}, {}
+ if name:
+ self.named[name] = (rule, None)
+
+ def build(self, _name, *anon, **args):
+ ''' Return a string that matches a named route. Use keyword arguments
+ to fill out named wildcards. Remaining arguments are appended as a
+ query string. Raises RouteBuildError or KeyError.'''
+ if _name not in self.named:
+ raise RouteBuildError("No route with that name.", _name)
+ rule, pairs = self.named[_name]
+ if not pairs:
+ token = self.syntax.split(rule)
+ parts = [p.replace('\\:',':') for p in token[::3]]
+ names = token[1::3]
+ if len(parts) > len(names): names.append(None)
+ pairs = zip(parts, names)
+ self.named[_name] = (rule, pairs)
+ try:
+ anon = list(anon)
+ url = [s if k is None
+ else s+str(args.pop(k)) if k else s+str(anon.pop())
+ for s, k in pairs]
+ except IndexError:
+ msg = "Not enough arguments to fill out anonymous wildcards."
+ raise RouteBuildError(msg)
+ except KeyError, e:
+ raise RouteBuildError(*e.args)
+
+ if args: url += ['?', urlencode(args)]
+ return ''.join(url)
+
+ def match(self, environ):
+ ''' Return a (target, url_agrs) tuple or raise HTTPError(404/405). '''
+ targets, urlargs = self._match_path(environ)
+ if not targets:
+ raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO']))
+ method = environ['REQUEST_METHOD'].upper()
+ if method in targets:
+ return targets[method], urlargs
+ if method == 'HEAD' and 'GET' in targets:
+ return targets['GET'], urlargs
+ if 'ANY' in targets:
+ return targets['ANY'], urlargs
+ allowed = [verb for verb in targets if verb != 'ANY']
+ if 'GET' in allowed and 'HEAD' not in allowed:
+ allowed.append('HEAD')
+ raise HTTPError(405, "Method not allowed.",
+ header=[('Allow',",".join(allowed))])
+
+ def _match_path(self, environ):
+ ''' Optimized PATH_INFO matcher. '''
+ path = environ['PATH_INFO'] or '/'
+ # Assume we are in a warm state. Search compiled rules first.
+ match = self.static.get(path)
+ if match: return match, {}
+ for combined, rules in self.dynamic:
+ match = combined.match(path)
+ if not match: continue
+ gpat, match = rules[match.lastindex - 1]
+ return match, gpat.match(path).groupdict() if gpat else {}
+ # Lazy-check if we are really in a warm state. If yes, stop here.
+ if self.static or self.dynamic or not self.routes: return None, {}
+ # Cold state: We have not compiled any rules yet. Do so and try again.
+ if not environ.get('wsgi.run_once'):
+ self._compile()
+ return self._match_path(environ)
+ # For run_once (CGI) environments, don't compile. Just check one by one.
+ epath = path.replace(':','\\:') # Turn path into its own static rule.
+ match = self.routes.get(epath) # This returns static rule only.
+ if match: return match, {}
+ for rule in self.rules:
+ #: Skip static routes to reduce re.compile() calls.
+ if rule.count(':') < rule.count('\\:'): continue
+ match = self._compile_pattern(rule).match(path)
+ if match: return self.routes[rule], match.groupdict()
+ return None, {}
+
+ def _compile(self):
+ ''' Prepare static and dynamic search structures. '''
+ self.static = {}
+ self.dynamic = []
+ def fpat_sub(m):
+ return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:'
+ for rule in self.rules:
+ target = self.routes[rule]
+ if not self.syntax.search(rule):
+ self.static[rule.replace('\\:',':')] = target
+ continue
+ gpat = self._compile_pattern(rule)
+ fpat = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, gpat.pattern)
+ gpat = gpat if gpat.groupindex else None
+ try:
+ combined = '%s|(%s)' % (self.dynamic[-1][0].pattern, fpat)
+ self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1])
+ self.dynamic[-1][1].append((gpat, target))
+ except (AssertionError, IndexError), e: # AssertionError: Too many groups
+ self.dynamic.append((re.compile('(^%s$)'%fpat),
+ [(gpat, target)]))
+ except re.error, e:
+ raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e))
+
+ def _compile_pattern(self, rule):
+ ''' Return a regular expression with named groups for each wildcard. '''
+ out = ''
+ for i, part in enumerate(self.syntax.split(rule)):
+ if i%3 == 0: out += re.escape(part.replace('\\:',':'))
+ elif i%3 == 1: out += '(?P<%s>' % part if part else '(?:'
+ else: out += '%s)' % (part or '[^/]+')
+ return re.compile('^%s$'%out)
+
+
+
+
+
+
+###############################################################################
+# Application Object ###########################################################
+###############################################################################
+
+
+class Bottle(object):
+ """ WSGI application """
+
+ def __init__(self, catchall=True, autojson=True, config=None):
+ """ Create a new bottle instance.
+ You usually don't do that. Use `bottle.app.push()` instead.
+ """
+ self.routes = [] # List of installed routes including metadata.
+ self.router = Router() # Maps requests to self.route indices.
+ self.ccache = {} # Cache for callbacks with plugins applied.
+
+ self.plugins = [] # List of installed plugins.
+
+ self.mounts = {}
+ self.error_handler = {}
+ #: If true, most exceptions are catched and returned as :exc:`HTTPError`
+ self.catchall = catchall
+ self.config = config or {}
+ self.serve = True
+ # Default plugins
+ self.hooks = self.install(HooksPlugin())
+ self.typefilter = self.install(TypeFilterPlugin())
+ if autojson:
+ self.install(JSONPlugin())
+ self.install(TemplatePlugin())
+
+ def optimize(self, *a, **ka):
+ depr("Bottle.optimize() is obsolete.")
+
+ def mount(self, app, prefix, **options):
+ ''' Mount an application to a specific URL prefix. The prefix is added
+ to SCIPT_PATH and removed from PATH_INFO before the sub-application
+ is called.
+
+ :param app: an instance of :class:`Bottle`.
+ :param prefix: path prefix used as a mount-point.
+
+ All other parameters are passed to the underlying :meth:`route` call.
+ '''
+ if not isinstance(app, Bottle):
+ raise TypeError('Only Bottle instances are supported for now.')
+ prefix = '/'.join(filter(None, prefix.split('/')))
+ if not prefix:
+ raise TypeError('Empty prefix. Perhaps you want a merge()?')
+ for other in self.mounts:
+ if other.startswith(prefix):
+ raise TypeError('Conflict with existing mount: %s' % other)
+ path_depth = prefix.count('/') + 1
+ options.setdefault('method', 'ANY')
+ options.setdefault('skip', True)
+ self.mounts[prefix] = app
+ @self.route('/%s/:#.*#' % prefix, **options)
+ def mountpoint():
+ request.path_shift(path_depth)
+ return app._handle(request.environ)
+
+ def add_filter(self, ftype, func):
+ depr("Filters are deprecated and can be replaced with plugins.") #0.9
+ self.typefilter.add(ftype, func)
+
+ def install(self, plugin):
+ ''' Add a plugin to the list of plugins and prepare it for beeing
+ applied to all routes of this application. A plugin may be a simple
+ decorator or an object that implements the :class:`Plugin` API.
+ '''
+ if hasattr(plugin, 'setup'): plugin.setup(self)
+ if not callable(plugin) and not hasattr(plugin, 'apply'):
+ raise TypeError("Plugins must be callable or implement .apply()")
+ self.plugins.append(plugin)
+ self.reset()
+ return plugin
+
+ def uninstall(self, plugin):
+ ''' Uninstall plugins. Pass an instance to remove a specific plugin.
+ Pass a type object to remove all plugins that match that type.
+ Subclasses are not removed. Pass a string to remove all plugins with
+ a matching ``name`` attribute. Pass ``True`` to remove all plugins.
+ The list of affected plugins is returned. '''
+ removed, remove = [], plugin
+ for i, plugin in list(enumerate(self.plugins))[::-1]:
+ if remove is True or remove is plugin or remove is type(plugin) \
+ or getattr(plugin, 'name', True) == remove:
+ removed.append(plugin)
+ del self.plugins[i]
+ if hasattr(plugin, 'close'): plugin.close()
+ if removed: self.reset()
+ return removed
+
+ def reset(self, id=None):
+ ''' Reset all routes (force plugins to be re-applied) and clear all
+ caches. If an ID is given, only that specific route is affected. '''
+ if id is None: self.ccache.clear()
+ else: self.ccache.pop(id, None)
+ if DEBUG:
+ for route in self.routes:
+ if route['id'] not in self.ccache:
+ self.ccache[route['id']] = self._build_callback(route)
+
+ def close(self):
+ ''' Close the application and all installed plugins. '''
+ for plugin in self.plugins:
+ if hasattr(plugin, 'close'): plugin.close()
+ self.stopped = True
+
+ def match(self, environ):
+ """ (deprecated) Search for a matching route and return a
+ (callback, urlargs) tuple.
+ The first element is the associated route callback with plugins
+ applied. The second value is a dictionary with parameters extracted
+ from the URL. The :class:`Router` raises :exc:`HTTPError` (404/405)
+ on a non-match."""
+ depr("This method will change semantics in 0.10.")
+ return self._match(environ)
+
+ def _match(self, environ):
+ handle, args = self.router.match(environ)
+ environ['route.handle'] = handle # TODO move to router?
+ environ['route.url_args'] = args
+ try:
+ return self.ccache[handle], args
+ except KeyError:
+ config = self.routes[handle]
+ callback = self.ccache[handle] = self._build_callback(config)
+ return callback, args
+
+ def _build_callback(self, config):
+ ''' Apply plugins to a route and return a new callable. '''
+ wrapped = config['callback']
+ plugins = self.plugins + config['apply']
+ skip = config['skip']
+ try:
+ for plugin in reversed(plugins):
+ if True in skip: break
+ if plugin in skip or type(plugin) in skip: continue
+ if getattr(plugin, 'name', True) in skip: continue
+ if hasattr(plugin, 'apply'):
+ wrapped = plugin.apply(wrapped, config)
+ else:
+ wrapped = plugin(wrapped)
+ if not wrapped: break
+ functools.update_wrapper(wrapped, config['callback'])
+ return wrapped
+ except RouteReset: # A plugin may have changed the config dict inplace.
+ return self._build_callback(config) # Apply all plugins again.
+
+ def get_url(self, routename, **kargs):
+ """ Return a string that matches a named route """
+ scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
+ location = self.router.build(routename, **kargs).lstrip('/')
+ return urljoin(urljoin('/', scriptname), location)
+
+ def route(self, path=None, method='GET', callback=None, name=None,
+ apply=None, skip=None, **config):
+ """ A decorator to bind a function to a request URL. Example::
+
+ @app.route('/hello/:name')
+ def hello(name):
+ return 'Hello %s' % name
+
+ The ``:name`` part is a wildcard. See :class:`Router` for syntax
+ details.
+
+ :param path: Request path or a list of paths to listen to. If no
+ path is specified, it is automatically generated from the
+ signature of the function.
+ :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
+ methods to listen to. (default: `GET`)
+ :param callback: An optional shortcut to avoid the decorator
+ syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
+ :param name: The name for this route. (default: None)
+ :param apply: A decorator or plugin or a list of plugins. These are
+ applied to the route callback in addition to installed plugins.
+ :param skip: A list of plugins, plugin classes or names. Matching
+ plugins are not installed to this route. ``True`` skips all.
+
+ Any additional keyword arguments are stored as route-specific
+ configuration and passed to plugins (see :meth:`Plugin.apply`).
+ """
+ if callable(path): path, callback = None, path
+
+ plugins = makelist(apply)
+ skiplist = makelist(skip)
+ if 'decorate' in config:
+ depr("The 'decorate' parameter was renamed to 'apply'") # 0.9
+ plugins += makelist(config.pop('decorate'))
+ if config.pop('no_hooks', False):
+ depr("The no_hooks parameter is no longer used. Add 'hooks' to the"\
+ " list of skipped plugins instead.") # 0.9
+ skiplist.append('hooks')
+ static = config.get('static', False) # depr 0.9
+
+ def decorator(callback):
+ for rule in makelist(path) or yieldroutes(callback):
+ for verb in makelist(method):
+ verb = verb.upper()
+ cfg = dict(rule=rule, method=verb, callback=callback,
+ name=name, app=self, config=config,
+ apply=plugins, skip=skiplist)
+ self.routes.append(cfg)
+ cfg['id'] = self.routes.index(cfg)
+ self.router.add(rule, verb, cfg['id'], name=name, static=static)
+ if DEBUG: self.ccache[cfg['id']] = self._build_callback(cfg)
+ return callback
+
+ return decorator(callback) if callback else decorator
+
+ def get(self, path=None, method='GET', **options):
+ """ Equals :meth:`route`. """
+ return self.route(path, method, **options)
+
+ def post(self, path=None, method='POST', **options):
+ """ Equals :meth:`route` with a ``POST`` method parameter. """
+ return self.route(path, method, **options)
+
+ def put(self, path=None, method='PUT', **options):
+ """ Equals :meth:`route` with a ``PUT`` method parameter. """
+ return self.route(path, method, **options)
+
+ def delete(self, path=None, method='DELETE', **options):
+ """ Equals :meth:`route` with a ``DELETE`` method parameter. """
+ return self.route(path, method, **options)
+
+ def error(self, code=500):
+ """ Decorator: Register an output handler for a HTTP error code"""
+ def wrapper(handler):
+ self.error_handler[int(code)] = handler
+ return handler
+ return wrapper
+
+ def hook(self, name):
+ """ Return a decorator that attaches a callback to a hook. """
+ def wrapper(func):
+ self.hooks.add(name, func)
+ return func
+ return wrapper
+
+ def add_hook(self, name, func):
+ depr("Call Bottle.hooks.add() instead.") #0.9
+ self.hooks.add(name, func)
+
+ def remove_hook(self, name, func):
+ depr("Call Bottle.hooks.remove() instead.") #0.9
+ self.hooks.remove(name, func)
+
+ def handle(self, path, method='GET'):
+ """ (deprecated) Execute the first matching route callback and return
+ the result. :exc:`HTTPResponse` exceptions are catched and returned.
+ If :attr:`Bottle.catchall` is true, other exceptions are catched as
+ well and returned as :exc:`HTTPError` instances (500).
+ """
+ depr("This method will change semantics in 0.10. Try to avoid it.")
+ if isinstance(path, dict):
+ return self._handle(path)
+ return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()})
+
+ def _handle(self, environ):
+ if not self.serve:
+ depr("Bottle.serve will be removed in 0.10.")
+ return HTTPError(503, "Server stopped")
+ try:
+ callback, args = self._match(environ)
+ return callback(**args)
+ except HTTPResponse, r:
+ return r
+ except RouteReset: # Route reset requested by the callback or a plugin.
+ del self.ccache[handle]
+ return self._handle(environ) # Try again.
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception, e:
+ if not self.catchall: raise
+ return HTTPError(500, "Internal Server Error", e, format_exc(10))
+
+ def _cast(self, out, request, response, peek=None):
+ """ Try to convert the parameter into something WSGI compatible and set
+ correct HTTP headers when possible.
+ Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
+ iterable of strings and iterable of unicodes
+ """
+
+ # Empty output is done here
+ if not out:
+ response.headers['Content-Length'] = 0
+ return []
+ # Join lists of byte or unicode strings. Mixed lists are NOT supported
+ if isinstance(out, (tuple, list))\
+ and isinstance(out[0], (bytes, unicode)):
+ out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
+ # Encode unicode strings
+ if isinstance(out, unicode):
+ out = out.encode(response.charset)
+ # Byte Strings are just returned
+ if isinstance(out, bytes):
+ response.headers['Content-Length'] = str(len(out))
+ return [out]
+ # HTTPError or HTTPException (recursive, because they may wrap anything)
+ if isinstance(out, HTTPError):
+ out.apply(response)
+ out = self.error_handler.get(out.status, repr)(out)
+ if isinstance(out, HTTPResponse):
+ depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9
+ return self._cast(out, request, response)
+ if isinstance(out, HTTPResponse):
+ out.apply(response)
+ return self._cast(out.output, request, response)
+
+ # File-like objects.
+ if hasattr(out, 'read'):
+ if 'wsgi.file_wrapper' in request.environ:
+ return request.environ['wsgi.file_wrapper'](out)
+ elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
+ return WSGIFileWrapper(out)
+
+ # Handle Iterables. We peek into them to detect their inner type.
+ try:
+ out = iter(out)
+ first = out.next()
+ while not first:
+ first = out.next()
+ except StopIteration:
+ return self._cast('', request, response)
+ except HTTPResponse, e:
+ first = e
+ except Exception, e:
+ first = HTTPError(500, 'Unhandled exception', e, format_exc(10))
+ if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\
+ or not self.catchall:
+ raise
+ # These are the inner types allowed in iterator or generator objects.
+ if isinstance(first, HTTPResponse):
+ return self._cast(first, request, response)
+ if isinstance(first, bytes):
+ return itertools.chain([first], out)
+ if isinstance(first, unicode):
+ return itertools.imap(lambda x: x.encode(response.charset),
+ itertools.chain([first], out))
+ return self._cast(HTTPError(500, 'Unsupported response type: %s'\
+ % type(first)), request, response)
+
+ def wsgi(self, environ, start_response):
+ """ The bottle WSGI-interface. """
+ try:
+ environ['bottle.app'] = self
+ request.bind(environ)
+ response.bind()
+ out = self._handle(environ)
+ out = self._cast(out, request, response)
+ # rfc2616 section 4.3
+ if response.status in (100, 101, 204, 304) or request.method == 'HEAD':
+ if hasattr(out, 'close'): out.close()
+ out = []
+ status = '%d %s' % (response.status, HTTP_CODES[response.status])
+ start_response(status, response.headerlist)
+ return out
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception, e:
+ if not self.catchall: raise
+ err = '<h1>Critical error while processing request: %s</h1>' \
+ % environ.get('PATH_INFO', '/')
+ if DEBUG:
+ err += '<h2>Error:</h2>\n<pre>%s</pre>\n' % repr(e)
+ err += '<h2>Traceback:</h2>\n<pre>%s</pre>\n' % format_exc(10)
+ environ['wsgi.errors'].write(err) #TODO: wsgi.error should not get html
+ start_response('500 INTERNAL SERVER ERROR', [('Content-Type', 'text/html')])
+ return [tob(err)]
+
+ def __call__(self, environ, start_response):
+ return self.wsgi(environ, start_response)
+
+
+
+
+
+
+###############################################################################
+# HTTP and WSGI Tools ##########################################################
+###############################################################################
+
+
+class Request(threading.local, DictMixin):
+ """ Represents a single HTTP request using thread-local attributes.
+ The Request object wraps a WSGI environment and can be used as such.
+ """
+ def __init__(self, environ=None):
+ """ Create a new Request instance.
+
+ You usually don't do this but use the global `bottle.request`
+ instance instead.
+ """
+ self.bind(environ or {},)
+
+ def bind(self, environ):
+ """ Bind a new WSGI environment.
+
+ This is done automatically for the global `bottle.request`
+ instance on every request.
+ """
+ self.environ = environ
+ # These attributes are used anyway, so it is ok to compute them here
+ self.path = '/' + environ.get('PATH_INFO', '/').lstrip('/')
+ self.method = environ.get('REQUEST_METHOD', 'GET').upper()
+
+ @property
+ def _environ(self):
+ depr("Request._environ renamed to Request.environ")
+ return self.environ
+
+ def copy(self):
+ ''' Returns a copy of self '''
+ return Request(self.environ.copy())
+
+ def path_shift(self, shift=1):
+ ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
+
+ :param shift: The number of path fragments to shift. May be negative
+ to change the shift direction. (default: 1)
+ '''
+ script_name = self.environ.get('SCRIPT_NAME','/')
+ self['SCRIPT_NAME'], self.path = path_shift(script_name, self.path, shift)
+ self['PATH_INFO'] = self.path
+
+ def __getitem__(self, key): return self.environ[key]
+ def __delitem__(self, key): self[key] = ""; del(self.environ[key])
+ def __iter__(self): return iter(self.environ)
+ def __len__(self): return len(self.environ)
+ def keys(self): return self.environ.keys()
+ def __setitem__(self, key, value):
+ """ Shortcut for Request.environ.__setitem__ """
+ self.environ[key] = value
+ todelete = []
+ if key in ('PATH_INFO','REQUEST_METHOD'):
+ self.bind(self.environ)
+ elif key == 'wsgi.input': todelete = ('body','forms','files','params')
+ elif key == 'QUERY_STRING': todelete = ('get','params')
+ elif key.startswith('HTTP_'): todelete = ('headers', 'cookies')
+ for key in todelete:
+ if 'bottle.' + key in self.environ:
+ del self.environ['bottle.' + key]
+
+ @DictProperty('environ', 'bottle.urlparts', read_only=True)
+ def urlparts(self):
+ ''' Return a :class:`urlparse.SplitResult` tuple that can be used
+ to reconstruct the full URL as requested by the client.
+ The tuple contains: (scheme, host, path, query_string, fragment).
+ The fragment is always empty because it is not visible to the server.
+ '''
+ env = self.environ
+ http = env.get('wsgi.url_scheme', 'http')
+ host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
+ if not host:
+ # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
+ host = env.get('SERVER_NAME', '127.0.0.1')
+ port = env.get('SERVER_PORT')
+ if port and port != ('80' if http == 'http' else '443'):
+ host += ':' + port
+ spath = self.environ.get('SCRIPT_NAME','').rstrip('/') + '/'
+ rpath = self.path.lstrip('/')
+ path = urlquote(urljoin(spath, rpath))
+ return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
+
+ @property
+ def url(self):
+ """ Full URL as requested by the client. """
+ return self.urlparts.geturl()
+
+ @property
+ def fullpath(self):
+ """ Request path including SCRIPT_NAME (if present). """
+ return urlunquote(self.urlparts[2])
+
+ @property
+ def query_string(self):
+ """ The part of the URL following the '?'. """
+ return self.environ.get('QUERY_STRING', '')
+
+ @property
+ def content_length(self):
+ """ Content-Length header as an integer, -1 if not specified """
+ return int(self.environ.get('CONTENT_LENGTH', '') or -1)
+
+ @property
+ def header(self):
+ depr("The Request.header property was renamed to Request.headers")
+ return self.headers
+
+ @DictProperty('environ', 'bottle.headers', read_only=True)
+ def headers(self):
+ ''' Request HTTP Headers stored in a :class:`HeaderDict`. '''
+ return WSGIHeaderDict(self.environ)
+
+ @DictProperty('environ', 'bottle.get', read_only=True)
+ def GET(self):
+ """ The QUERY_STRING parsed into an instance of :class:`MultiDict`. """
+ data = parse_qs(self.query_string, keep_blank_values=True)
+ get = self.environ['bottle.get'] = MultiDict()
+ for key, values in data.iteritems():
+ for value in values:
+ get[key] = value
+ return get
+
+ @DictProperty('environ', 'bottle.post', read_only=True)
+ def POST(self):
+ """ The combined values from :attr:`forms` and :attr:`files`. Values are
+ either strings (form values) or instances of
+ :class:`cgi.FieldStorage` (file uploads).
+ """
+ post = MultiDict()
+ safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
+ for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
+ if key in self.environ: safe_env[key] = self.environ[key]
+ if NCTextIOWrapper:
+ fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n')
+ else:
+ fb = self.body
+ data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True)
+ for item in data.list or []:
+ post[item.name] = item if item.filename else item.value
+ return post
+
+ @DictProperty('environ', 'bottle.forms', read_only=True)
+ def forms(self):
+ """ POST form values parsed into an instance of :class:`MultiDict`.
+
+ This property contains form values parsed from an `url-encoded`
+ or `multipart/form-data` encoded POST request bidy. The values are
+ native strings.
+ """
+ forms = MultiDict()
+ for name, item in self.POST.iterallitems():
+ if not hasattr(item, 'filename'):
+ forms[name] = item
+ return forms
+
+ @DictProperty('environ', 'bottle.files', read_only=True)
+ def files(