apocrypha 2.2.0
A lightweight, flexible JSON server and client
Keywords: database,json

=========
Apocrypha
=========

.. image:: https://img.shields.io/pypi/v/apocrypha.svg
   :target: https://pypi.python.org/pypi/apocrypha
   :alt: PyPI Version

.. image:: https://img.shields.io/pypi/pyversions/apocrypha.svg
   :target: https://pypi.python.org/pypi/apocrypha
   :alt: Supported Python Versions

.. image:: https://travis-ci.org/Gandalf-/apocrypha.svg?branch=master
    :target: https://travis-ci.org/Gandalf-/apocrypha

.. image:: https://codecov.io/gh/Gandalf-/apocrypha/branch/master/graph/badge.svg
  :target: https://codecov.io/gh/Gandalf-/apocrypha

Apocrypha is a lightweight, flexible JSON server and client written in Python
3. It includes a client library for easy interaction through Python, and it's
simple query format allows APIs to easily written in other languages.

You can install Apocrypha with pip: ``pip3 install apocrypha``

Then you're ready to start the server: ``python3 -m apocrypha.server``

If you're comfortable with Haskell, there is a `Haskell Implementation`_.

.. _Haskell Implementation: https://github.com/Gandalf-/apocrypha-haskell

Features
========

- **multithreaded** Thread safe server and client. All queries are atomic.

- **caching** Writes clear the cache, any query will be served out of the
  cache if possible.

- **nosql** No schemas, no overhead, just your data in the format you
  provided.

- **json** Serve any existing JSON file to the network or start from
  scratch. Supports unlimited nested dictionaries.

- **fast** Serve up to 20,000 queries per second on cache hit heavy workloads.
  Database is kept in memory, no disk reads are made after startup.

- **persistance** Writes are queued and saved to disk once per second.

- **lightweight** Less than 2,000 lines of Python and no external dependencies
  outside of the standard library.

-----

Example Python API usage, check ``pydoc3 apocrypha.client`` for full usage and
more examples.

.. code-block:: python

  from apocrypha.client import Client
  db = Client()

  # apocrypha supports lists, strings, and dictionaries
  for i in range(0, 100):
    db.append('numbers', value=i)

  print(db.get('numbers')[:10])

  # nested dictionaries are allowed!
  customers = {
    'alice': {
      'age': 30
    },
    'bob' : {
      'age': 20
    }
  }

  db.set('customers', value=customers)
  print(db.keys('customers'))

  # query for sub keys with a simple syntax
  print(db.get('customers', 'alice', 'age'))

  for customer in db.keys('customers'):
    print(db.get('customers', customer, 'age'))

-----

A simple C client is available here_ if you want faster start up times than
Python for client applications, like shell scripts.

.. _here: https://github.com/Gandalf-/DotFiles/blob/master/bin/d.c

The network protocol is simple: send the length of the message in bytes, then
the message. Query elements are delimited by newlines. Each message ends in a
newline, and newlines are included in the length calculation of the message.


index
=====

index further into the database through a key, then recursively display all
keys and values under the key. this is the usual way to traverse the database
and gather information

::

  (dict a, str b, b in a) => a b -> IO

  $ d apples granny = good
  $ d apples
  {'granny': 'good'}
  $ d apples granny
  good

append
======

append a list or string to an existing string or list. create the left side if
it doesn't already exist

::

  (none a | str a | list a, str b | list b) => a + b -> none | error

  $ d toppings = mushrooms
  $ d toppings + pineapple
  $ d toppings
  mushrooms
  pineapple


remove
======

remove one or more elements from a list. if the resulting list now only
contains one element, it's converted to a singleton

::

  (list a, str b | list b, b in a) => a - b -> none | error

  $ d sweets = cake pie pizza
  $ d sweets - pizza
  $ d sweets
  cake
  pie

assign
======

assign the value of an element. if multiple arguments are given on the right
side of the assignment, the result is list assignment

::

  (any a, str b | list b) => a = b -> none

  $ d apple = sauce pie
  $ d apple
  sauce
  pie

search
======

recursively search the current level for a value. displays all the keys that
correspond have the value's value

::

  (str a) => IO

  $ d rasp = berry
  $ d blue = berry
  $ d @ berry
  rasp
  blue

keys
====

show the keys immediately under this value. doesn't recursively print all keys
and values underneathe

::

  dict a => a --keys -> IO | error

  $ d stone sand = weak
  $ d stone lime = tough
  $ d stone --keys
  sand
  lime

set
===

replace the value of an index with raw JSON

::

  (any a, str b, JSON b) => a --set b -> none | error

  $ d pasta --set '["spaghetti", "lasgna"]'
  $ d pasta
  spaghetti
  lasagna

edit
====

dump the raw JSON value of a key.

::

  any a => a --edit -> IO

  $ d pasta = spaghetti sauce
  $ d pasta --edit
  '["spaghetti", "sauce"]'

delete
======

delete any element from it's parent dictionary

::

  any a => a --del -> none

  $ d apple sauce = good
  $ d apple pie = great
  $ d apple sauce --del
  $ d apple
  {'pie': 'great'}