diff options
author | Prashanth Pai <ppai@redhat.com> | 2016-06-09 15:19:57 +0530 |
---|---|---|
committer | Prashanth Pai <ppai@redhat.com> | 2016-06-09 16:33:13 +0530 |
commit | f7f2dafb1e3d09017de6968a2a553d3e8b6c765c (patch) | |
tree | dc46d31d639a321100724ca83d66862333892085 | |
parent | 972c24f8b11d5a3e7e6fc341453d9733b2bb47b5 (diff) |
Revamp and complete API documentation
Created .rst files that sphinx can use to auto-generate entire
API documentation from doc strings present in code. This can be easily
rendered and hosted on ReadTheDocs website.
Change-Id: If1a569bdeaaba21919ac77ba8bd4967dfec22603
Signed-off-by: Prashanth Pai <ppai@redhat.com>
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 68 | ||||
-rw-r--r-- | doc/Makefile | 225 | ||||
-rw-r--r-- | doc/api-reference.rst | 22 | ||||
-rw-r--r-- | doc/conf.py | 341 | ||||
-rw-r--r-- | doc/index.rst | 56 | ||||
-rwxr-xr-x | gluster/gfapi.py | 267 |
7 files changed, 888 insertions, 92 deletions
@@ -7,3 +7,4 @@ cover pycscope.* build dist +doc/_build @@ -1,59 +1,57 @@ -# Overview +### Overview -Python bindings for the [GlusterFS](http://www.gluster.org) libgfapi interface +This is the official python bindings for the +[GlusterFS](http://www.gluster.org) libgfapi C library interface. -# Installation +Complete API reference and documentation can be found at +[ReadTheDocs](http://libgfapi-python.readthedocs.io/) -1) Clone the git repo +### Installation ``` $ git clone https://review.gluster.org/libgfapi-python $ cd libgfapi-python -``` - -2) Run the setup script - -``` $ sudo python setup.py install ``` -# Usage + +### Example Usage ```python from gluster import gfapi -import os -## Create virtual mount -volume = gfapi.Volume(....) +# Create virtual mount +volume = gfapi.Volume('10.7.1.99', 'datavolume') volume.mount() -## Create a new directory -volume.mkdir('newdir', 0755) +# Create directory +volume.mkdir('dir1', 0755) -## Create a new directory recursively -volume.makedirs('/somedir/dir',0755) +# List directories +volume.listdir('/') -## Delete a directory -volume.rmdir('/somedir/dir') +# Create new file and write to it +with volume.fopen('somefile.txt', 'w+') as f: + f.write("Winter is coming.") -## Create a file from a string using fopen. w+: open file for reading and writing -with volume.fopen('somefile.txt', 'w+') as fd: - fd.write("shadowfax") +# Open and read file +with volume.fopen('somefile.txt', 'r') as f: + print f.read() -## Read a file. r: open file for only reading -with volume.fopen('somefile.txt', 'r') as fd: - print fd.read() - -## Write to an existing file. a+: open a file for reading and appending -with volume.fopen('somefile.txt','a+') as fd: - fd.write("\n some new line in our file") - -## Delete a file +# Delete file volume.unlink('somefile.txt') -## Unmount a volume +# Unmount the volume volume.unmount() - ``` -# Development -* [Developer Guide](doc/markdown/dev_guide.md) +### TODOs + +* Submit to pypy to enable installing using pip +* Add support for py3 +* Implement async I/O APIs +* Implement file locking APIs +* Implement os.scandir() like API that leverages d\_type +* Improve Volume.walk() by leveraging scandir. +* Test and allow protocols other than TCP such as rdma and socket. + +Please follow the [Developer Guide](doc/markdown/dev_guide.md) to contribute code. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..858fc5b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/libgfapi-python.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/libgfapi-python.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/libgfapi-python" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/libgfapi-python" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/doc/api-reference.rst b/doc/api-reference.rst new file mode 100644 index 0000000..98b0427 --- /dev/null +++ b/doc/api-reference.rst @@ -0,0 +1,22 @@ +API Reference +============= + +Volume Class +------------ + +.. autoclass:: gluster.gfapi.Volume + :members: + :undoc-members: + :noindex: + + .. automethod:: gluster.gfapi.Volume.__init__ + +File Class +---------- + +.. autoclass:: gluster.gfapi.File + :members: + :undoc-members: + :noindex: + + .. automethod:: gluster.gfapi.File.__init__ diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..4f3d8e0 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +# +# libgfapi-python documentation build configuration file, created by +# sphinx-quickstart on Wed Jun 8 16:29:53 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys +import mock + +sys.path.insert(0, os.path.abspath('..')) + +for module in ('ctypes', 'libgfapi.so', 'libgfapi.so.0'): + sys.modules[module] = mock.MagicMock() + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'libgfapi-python' +copyright = u'2016, Red Hat, Inc.' +author = u'Red Hat, Inc.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# "<project> v<release> documentation" by default. +# +# html_title = u'libgfapi-python v0' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'libgfapi-pythondoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'libgfapi-python.tex', u'libgfapi-python Documentation', + u'Red Hat Inc.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'libgfapi-python', u'libgfapi-python Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'libgfapi-python', u'libgfapi-python Documentation', + author, 'libgfapi-python', 'Python bindings for GlusterFS libgfapi', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..2ff1263 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,56 @@ +libgfapi-python +=============== + +This is the official python bindings for `GlusterFS <http://www.gluster.org>`_ +libgfapi C library interface. + +Installation +------------ + +Install from source: + +.. code-block:: console + + $ git clone https://review.gluster.org/libgfapi-python + $ cd libgfapi-python + $ sudo python setup.py install + +Example Usage +------------- + +.. code-block:: python + + from gluster import gfapi + + # Create virtual mount + volume = gfapi.Volume('10.7.1.99', 'datavolume') + volume.mount() + + # Create directory + volume.mkdir('dir1', 0755) + + # List directories + volume.listdir('/') + + # Create new file and write to it + with volume.fopen('somefile.txt', 'w+') as f: + f.write("Winter is coming.") + + # Open and read file + with volume.fopen('somefile.txt', 'r') as f: + print f.read() + + # Delete file + volume.unlink('somefile.txt') + + # Unmount the volume + volume.unmount() + + +API Reference +------------- + +.. toctree:: + :maxdepth: 2 + + api-reference diff --git a/gluster/gfapi.py b/gluster/gfapi.py index c409f62..6fed798 100755 --- a/gluster/gfapi.py +++ b/gluster/gfapi.py @@ -43,6 +43,13 @@ _populate_mode_to_flags_dict() class File(object): def __init__(self, fd, path=None, mode=None): + """ + Create a File object equivalent to Python's built-in File object. + + :param fd: glfd pointer + :param path: Path of the file. This is optional. + :param mode: The I/O mode of the file. + """ self.fd = fd self.originalpath = path self._mode = mode @@ -61,25 +68,44 @@ class File(object): @property def fileno(self): + """ + Return the internal file descriptor (glfd) that is used by the + underlying implementation to request I/O operations. + """ # TODO: Make self.fd private (self._fd) return self.fd @property def mode(self): + """ + The I/O mode for the file. If the file was created using the + Volume.fopen() function, this will be the value of the mode + parameter. This is a read-only attribute. + """ return self._mode @property def name(self): + """ + If the file object was created using Volume.fopen(), + the name of the file. + """ return self.originalpath @property def closed(self): + """ + Bool indicating the current state of the file object. This is a + read-only attribute; the close() method changes the value. + """ return self._closed def close(self): """ Close the file. A closed file cannot be read or written any more. Calling close() more than once is allowed. + + :raises: OSError on failure """ if not self._closed: ret = api.glfs_close(self.fd) @@ -88,13 +114,32 @@ class File(object): raise OSError(err, os.strerror(err)) self._closed = True - def discard(self, offset, len): - ret = api.glfs_discard(self.fd, offset, len) + def discard(self, offset, length): + """ + This is similar to UNMAP command that is used to return the + unused/freed blocks back to the storage system. In this + implementation, fallocate with FALLOC_FL_PUNCH_HOLE is used to + eventually release the blocks to the filesystem. If the brick has + been mounted with '-o discard' option, then the discard request + will eventually reach the SCSI storage if the storage device + supports UNMAP. + + :param offset: Starting offset + :param len: Length or size in bytes to discard + :raises: OSError on failure + """ + ret = api.glfs_discard(self.fd, offset, length) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) def dup(self): + """ + Return a duplicate of File object. This duplicate File class instance + encapsulates a duplicate glfd obtained by invoking glfs_dup(). + + :raises: OSError on failure + """ dupfd = api.glfs_dup(self.fd) if not dupfd: err = ctypes.get_errno() @@ -112,6 +157,7 @@ class File(object): :param mode: Operation to be performed on the given range :param offset: Starting offset :param length: Size in bytes, starting at offset + :raises: OSError on failure """ ret = api.glfs_fallocate(self.fd, mode, offset, length) if ret < 0: @@ -123,6 +169,7 @@ class File(object): Change this file's mode :param mode: new mode + :raises: OSError on failure """ ret = api.glfs_fchmod(self.fd, mode) if ret < 0: @@ -135,6 +182,7 @@ class File(object): :param uid: new user id for file :param gid: new group id for file + :raises: OSError on failure """ ret = api.glfs_fchown(self.fd, uid, gid) if ret < 0: @@ -145,6 +193,8 @@ class File(object): """ Flush buffer cache pages pertaining to data, but not the ones pertaining to metadata. + + :raises: OSError on failure """ ret = api.glfs_fdatasync(self.fd) if ret < 0: @@ -170,6 +220,7 @@ class File(object): If size is non-zero, it is assumed the caller knows the size of xattr. :returns: Value of extended attribute corresponding to key specified. + :raises: OSError on failure """ if size == 0: size = api.glfs_fgetxattr(self.fd, key, None, size) @@ -193,6 +244,7 @@ class File(object): If size is non-zero, it is assumed the caller knows the size of the list. :returns: List of extended attributes. + :raises: OSError on failure """ if size == 0: size = api.glfs_flistxattr(self.fd, None, 0) @@ -229,13 +281,14 @@ class File(object): :param key: The key of extended attribute. :param value: The valiue of extended attribute. - :param flags: Possible values are 0 (default), 1 and 2 - 0: xattr will be created if it does not exist, or the - value will be replaced if the xattr exists. - 1: Perform a pure create, which fails if the named - attribute already exists. - 2: Perform a pure replace operation, which fails if the - named attribute does not already exist. + :param flags: Possible values are 0 (default), 1 and 2. + If 0 - xattr will be created if it does not exist, or + the value will be replaced if the xattr exists. If 1 - + it performs a pure create, which fails if the named + attribute already exists. If 2 - it performs a pure + replace operation, which fails if the named attribute + does not already exist. + :raises: OSError on failure """ ret = api.glfs_fsetxattr(self.fd, key, value, len(value), flags) if ret < 0: @@ -247,6 +300,7 @@ class File(object): Remove a extended attribute of the file. :param key: The key of extended attribute. + :raises: OSError on failure """ ret = api.glfs_fremovexattr(self.fd, key) if ret < 0: @@ -258,6 +312,7 @@ class File(object): Returns Stat object for this file. :return: Returns the stat information of the file. + :raises: OSError on failure """ s = api.Stat() rc = api.glfs_fstat(self.fd, ctypes.byref(s)) @@ -269,6 +324,8 @@ class File(object): def fsync(self): """ Flush buffer cache pages pertaining to data and metadata. + + :raises: OSError on failure """ ret = api.glfs_fsync(self.fd) if ret < 0: @@ -284,6 +341,7 @@ class File(object): extended part reads as null bytes. :param length: Length to truncate the file to in bytes. + :raises: OSError on failure """ ret = api.glfs_ftruncate(self.fd, length) if ret < 0: @@ -301,6 +359,7 @@ class File(object): to the current position and SEEK_END sets the position relative to the end of the file. :returns: the new offset position + :raises: OSError on failure """ ret = api.glfs_lseek(self.fd, pos, how) if ret < 0: @@ -315,6 +374,7 @@ class File(object): :param buflen: length of read buffer. If less than 0, then whole file is read. Default is -1. :returns: buffer of 'size' length + :raises: OSError on failure """ if size < 0: size = self.fgetsize() @@ -339,7 +399,8 @@ class File(object): it's invoked, readinto() copies data to an already allocated buffer passed to it. - Returns the number of bytes read (0 for EOF). + :returns: the number of bytes read (0 for EOF). + :raises: OSError on failure """ if type(buf) is bytearray: buf_ptr = (ctypes.c_ubyte * len(buf)).from_buffer(buf) @@ -358,6 +419,7 @@ class File(object): :param data: The data to be written to file. :returns: The size in bytes actually written + :raises: OSError on failure """ # creating a ctypes.c_ubyte buffer to handle converting bytearray # to the required C data type @@ -373,6 +435,13 @@ class File(object): return ret def zerofill(self, offset, length): + """ + Fill 'length' number of bytes with zeroes starting from 'offset'. + + :param offset: Start at offset location + :param length: Size/length in bytes + :raises: OSError on failure + """ ret = api.glfs_zerofill(self.fd, offset, length) if ret < 0: err = ctypes.get_errno() @@ -422,11 +491,12 @@ class Volume(object): :param log_level: Integer specifying the degree of verbosity. Higher the value, more verbose the logging. - TODO: Provide an interface where user can specify volfile directly - instead of providing host and other details. This is helpful in cases - where user wants to load some non default xlator on client side. For - example, aux-gfid-mount or mount volume as read-only. """ + # TODO: Provide an interface where user can specify volfile directly + # instead of providing host and other details. This is helpful in cases + # where user wants to load some non default xlator on client side. For + # example, aux-gfid-mount or mount volume as read-only. + # Add a reference so the module-level variable "api" doesn't # get yanked out from under us (see comment above File def'n). self._api = api @@ -451,11 +521,18 @@ class Volume(object): @property def mounted(self): + """ + Read-only attribute that returns True if the volume is mounted. + The value of the attribute is internally changed on invoking + mount() and umount() functions. + """ return self._mounted def mount(self): """ Mount a GlusterFS volume for use. + + :raises: LibgfapiException on failure """ if self.fs and self._mounted: # Already mounted @@ -493,6 +570,8 @@ class Volume(object): Provides users a way to free resources instead of just waiting for python garbage collector to call __del__() at some point later. + + :raises: LibgfapiException on failure """ if self.fs: ret = self._api.glfs_fini(self.fs) @@ -561,6 +640,7 @@ class Volume(object): Change the current working directory to the given path. :param path: Path to change current working directory to + :raises: OSError on failure """ ret = api.glfs_chdir(self.fs, path) if ret < 0: @@ -572,8 +652,8 @@ class Volume(object): Change mode of path :param path: the item to be modified - :mode: new mode - :returns: 0 if success, raises OSError if it fails + :param mode: new mode + :raises: OSError on failure """ ret = api.glfs_chmod(self.fs, path, mode) if ret < 0: @@ -584,10 +664,10 @@ class Volume(object): """ Change owner and group id of path - :param path: the item to be modified - :param uid: new user id for item - :param gid: new group id for item - :returns: 0 if success, raises OSError if it fails + :param path: the path to be modified + :param uid: new user id for path + :param gid: new group id for path + :raises: OSError on failure """ ret = api.glfs_chown(self.fs, path, uid, gid) if ret < 0: @@ -596,8 +676,10 @@ class Volume(object): def exists(self, path): """ - Test whether a path exists. - Returns False for broken symbolic links. + Returns True if path refers to an existing path. Returns False for + broken symbolic links. This function may return False if permission is + not granted to execute stat() on the requested file, even if the path + physically exists. """ try: self.stat(path) @@ -607,15 +689,15 @@ class Volume(object): def getatime(self, path): """ - Returns the last access time as reported by stat + Returns the last access time as reported by stat() """ return self.stat(path).st_atime def getctime(self, path): """ - Returns the time when changes were made to the path as reported by stat - This time is updated when changes are made to the file or dir's inode - or the contents of the file + Returns the time when changes were made to the path as reported by + stat(). This time is updated when changes are made to the file or + dir's inode or the contents of the file """ return self.stat(path).st_ctime @@ -634,15 +716,15 @@ class Volume(object): def getmtime(self, path): """ Returns the time when changes were made to the content of the path - as reported by stat + as reported by stat() """ return self.stat(path).st_mtime - def getsize(self, filename): + def getsize(self, path): """ - Return the size of a file, reported by stat() + Return the size of a file in bytes, reported by stat() """ - return self.stat(filename).st_size + return self.stat(path).st_size def getxattr(self, path, key, size=0): """ @@ -656,6 +738,7 @@ class Volume(object): If size is non-zero, it is assumed the caller knows the size of xattr. :returns: Value of extended attribute corresponding to key specified. + :raises: OSError on failure """ if size == 0: size = api.glfs_getxattr(self.fs, path, key, None, 0) @@ -672,7 +755,8 @@ class Volume(object): def isdir(self, path): """ - Test whether a path is an existing directory + Returns True if path is an existing directory. Returns False on all + failure cases including when path does not exist. """ try: s = self.stat(path) @@ -682,7 +766,8 @@ class Volume(object): def isfile(self, path): """ - Test whether a path is a regular file + Return True if path is an existing regular file. Returns False on all + failure cases including when path does not exist. """ try: s = self.stat(path) @@ -692,7 +777,9 @@ class Volume(object): def islink(self, path): """ - Test whether a path is a symbolic link + Return True if path refers to a directory entry that is a symbolic + link. Returns False on all failure cases including when path does + not exist. """ try: s = self.lstat(path) @@ -702,8 +789,10 @@ class Volume(object): def listdir(self, path): """ - Return list of entries in a given directory 'path'. - "." and ".." are not included, and the list is not sorted. + Return a list containing the names of the entries in the directory + given by path. The list is in arbitrary order. It does not include + the special entries '.' and '..' even if they are present in the + directory. """ dir_list = [] d = self.opendir(path) @@ -712,7 +801,7 @@ class Volume(object): if not isinstance(ent, api.Dirent): break name = ent.d_name[:ent.d_reclen] - if name not in [".", ".."]: + if name not in (".", ".."): dir_list.append(name) return dir_list @@ -726,6 +815,7 @@ class Volume(object): If size is non-zero, it is assumed the caller knows the size of the list. :returns: List of extended attribute keys. + :raises: OSError on failure """ if size == 0: size = api.glfs_listxattr(self.fs, path, None, 0) @@ -761,6 +851,8 @@ class Volume(object): Return stat information of path. If path is a symbolic link, then it returns information about the link itself, not the file that it refers to. + + :raises: OSError on failure """ s = api.Stat() rc = api.glfs_lstat(self.fs, path, ctypes.byref(s)) @@ -769,11 +861,17 @@ class Volume(object): raise OSError(err, os.strerror(err)) return s - def makedirs(self, name, mode=0777): + def makedirs(self, path, mode=0777): """ - Create directories defined in 'name' recursively. + Recursive directory creation function. Like mkdir(), but makes all + intermediate-level directories needed to contain the leaf directory. + The default mode is 0777 (octal). + + :raises: OSError if the leaf directory already exists or cannot be + created. Can also raise OSError if creation of any non-leaf + directories fails. """ - head, tail = os.path.split(name) + head, tail = os.path.split(path) if not tail: head, tail = os.path.split(head) if head and tail and not self.exists(head): @@ -784,11 +882,14 @@ class Volume(object): raise if tail == os.curdir: return - self.mkdir(name, mode) + self.mkdir(path, mode) def mkdir(self, path, mode=0777): """ - Create a directory + Create a directory named path with numeric mode mode. + The default mode is 0777 (octal). + + :raises: OSError on failure """ ret = api.glfs_mkdir(self.fs, path, mode) if ret < 0: @@ -803,9 +904,21 @@ class Volume(object): and it does NOT invoke glibc's fopen and does NOT do any kind of I/O bufferring as of today. + The most commonly-used values of mode are 'r' for reading, 'w' for + writing (truncating the file if it already exists), and 'a' for + appending. If mode is omitted, it defaults to 'r'. + + Modes 'r+', 'w+' and 'a+' open the file for updating (reading and + writing); note that 'w+' truncates the file. + + Append 'b' to the mode to open the file in binary mode but this has + no effect as of today. + :param path: Path of file to be opened :param mode: Mode to open the file with. This is a string. :returns: an instance of File class + :raises: OSError on failure to create/open file. + TypeError and ValueError if mode is invalid. """ if not isinstance(mode, basestring): raise TypeError("Mode must be a string") @@ -834,8 +947,10 @@ class Volume(object): :param flags: Integer which flags must include one of the following access modes: os.O_RDONLY, os.O_WRONLY, or os.O_RDWR. :param mode: specifies the permissions to use in case a new - file is created. - :returns: the raw glfd + file is created. The default mode is 0777 (octal) + :returns: the raw glfd (pointer to memory in C, number in python) + :raises: OSError on failure to create/open file. + TypeError if flags is not an integer. """ if not isinstance(flags, int): raise TypeError("flags must evaluate to an integer") @@ -856,6 +971,7 @@ class Volume(object): :param path: Path to the directory :returns: Returns a instance of Dir class + :raises: OSError on failure """ fd = api.glfs_opendir(self.fs, path) if not fd: @@ -865,10 +981,12 @@ class Volume(object): def readlink(self, path): """ - Read contents of symbolic link path. + Return a string representing the path to which the symbolic link + points. The result may be either an absolute or relative pathname. :param path: Path of symbolic link :returns: Contents of symlink + :raises: OSError on failure """ PATH_MAX = 4096 buf = ctypes.create_string_buffer(PATH_MAX) @@ -880,17 +998,20 @@ class Volume(object): def remove(self, path): """ - Remove (delete) the file path. If path is a directory, - OSError is raised. + Remove (delete) the file path. If path is a directory, OSError + is raised. This is identical to the unlink() function. + + :raises: OSError on failure """ return self.unlink(path) def removexattr(self, path, key): """ - Remove a extended attribute of the file. + Remove a extended attribute of the path. :param path: Path to the file or directory. :param key: The key of extended attribute. + :raises: OSError on failure """ ret = api.glfs_removexattr(self.fs, path, key) if ret < 0: @@ -899,7 +1020,11 @@ class Volume(object): def rename(self, src, dst): """ - Rename the file or directory from src to dst. + Rename the file or directory from src to dst. If dst is a directory, + OSError will be raised. If dst exists and is a file, it will be + replaced silently if the user has permission. + + :raises: OSError on failure """ ret = api.glfs_rename(self.fs, src, dst) if ret < 0: @@ -911,6 +1036,8 @@ class Volume(object): Remove (delete) the directory path. Only works when the directory is empty, otherwise, OSError is raised. In order to remove whole directory trees, rmtree() can be used. + + :raises: OSError on failure """ ret = api.glfs_rmdir(self.fs, path) if ret < 0: @@ -930,6 +1057,7 @@ class Volume(object): to fail; and exc is the exception that was raised. If ignore_errors is False and onerror is None, an exception is raised + :raises: OSError on failure if onerror is None """ if ignore_errors: def onerror(*args): @@ -963,6 +1091,8 @@ class Volume(object): setfsuid() changes the value of the caller's filesystem user ID-the user ID that the Linux kernel uses to check for all accesses to the filesystem. + + :raises: OSError on failure """ ret = api.glfs_setfsuid(uid) if ret < 0: @@ -974,6 +1104,8 @@ class Volume(object): setfsgid() changes the value of the caller's filesystem group ID-the group ID that the Linux kernel uses to check for all accesses to the filesystem. + + :raises: OSError on failure """ ret = api.glfs_setfsgid(gid) if ret < 0: @@ -987,13 +1119,15 @@ class Volume(object): :param path: Path to file or directory. :param key: The key of extended attribute. :param value: The valiue of extended attribute. - :param flags: Possible values are 0 (default), 1 and 2 - 0: xattr will be created if it does not exist, or the - value will be replaced if the xattr exists. - 1: Perform a pure create, which fails if the named - attribute already exists. - 2: Perform a pure replace operation, which fails if the - named attribute does not already exist. + :param flags: Possible values are 0 (default), 1 and 2. + If 0 - xattr will be created if it does not exist, or + the value will be replaced if the xattr exists. If 1 - + it performs a pure create, which fails if the named + attribute already exists. If 2 - it performs a pure + replace operation, which fails if the named attribute + does not already exist. + + :raises: OSError on failure """ ret = api.glfs_setxattr(self.fs, path, key, value, len(value), flags) if ret < 0: @@ -1003,6 +1137,8 @@ class Volume(object): def stat(self, path): """ Returns stat information of path. + + :raises: OSError on failure """ s = api.Stat() rc = api.glfs_stat(self.fs, path, ctypes.byref(s)) @@ -1015,6 +1151,14 @@ class Volume(object): """ Returns information about a mounted glusterfs volume. path is the pathname of any file within the mounted filesystem. + + :returns: An object whose attributes describe the filesystem on the + given path, and correspond to the members of the statvfs + structure, namely: f_bsize, f_frsize, f_blocks, f_bfree, + f_bavail, f_files, f_ffree, f_favail, f_fsid, f_flag, + and f_namemax. + + :raises: OSError on failure """ s = api.Statvfs() rc = api.glfs_statvfs(self.fs, path, ctypes.byref(s)) @@ -1026,6 +1170,8 @@ class Volume(object): def symlink(self, source, link_name): """ Create a symbolic link 'link_name' which points to 'source' + + :raises: OSError on failure """ ret = api.glfs_symlink(self.fs, source, link_name) if ret < 0: @@ -1036,8 +1182,7 @@ class Volume(object): """ Delete the file 'path' - :param path: file to be deleted - :returns: 0 if success, raises OSError if it fails + :raises: OSError on failure """ ret = api.glfs_unlink(self.fs, path) if ret < 0: @@ -1052,6 +1197,10 @@ class Volume(object): touch on the path.) Otherwise, times must be a 2-tuple of numbers, of the form (atime, mtime) which is used to set the access and modified times, respectively. + + + :raises: OSError on failure to change time. + TypeError if invalid times is passed. """ if times is None: now = time.time() @@ -1084,7 +1233,11 @@ class Volume(object): dirpath is the path to the directory, dirnames is a list of the names of the subdirectories in dirpath. filenames is a list of the names of the non-directiry files in dirpath + + :raises: OSError on failure if onerror is None """ + # TODO: Write a more efficient walk by leveraging d_type information + # returned in readdir. try: names = self.listdir(top) except OSError as err: |