redis-helper


Nameredis-helper JSON
Version 0.4.8 PyPI version JSON
download
home_pagehttps://github.com/kenjyco/redis-helper
SummaryEasily store, index, and modify Python dicts in Redis (with flexible searching)
upload_time2024-01-11 02:48:25
maintainer
docs_urlNone
authorKen
requires_python
licenseMIT
keywords redis cli command-line dictionary data database secondary index model prototype event logging dashboard easy modeling helper kenjyco
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
               Note: as of redis-helper v0.4.0, version 3.0 of redis-py is in use,
   which has backwards incompatible changes withe redis-py 2.x. See
   https://github.com/redis/redis-py/tree/70ef9ec68f9163c86d4cace2941e2f0ae4ce8525#upgrading-from-redis-py-2x-to-30

About
-----

Install redis-helper, create an instance of ``redis_helper.Collection``
(**the args/kwargs define the model**) and use the ``add``, ``get``,
``update``, ``delete``, and ``find`` methods to:

-  quickly store/retrieve/modify Python dicts in Redis
-  filter through indexed fields with simple/flexible find arguments
-  power real-time dashboards with metrics at a variety of time ranges
-  super-charge event logging and system debugging
-  build FAST prototypes and simulators
-  greatly simplify data access patterns throughout application

See the `request logging demo <https://asciinema.org/a/101422?t=1:10>`__
and `urls
demo <https://asciinema.org/a/75kl95ty9vg2jl93pfz9fbs9q?t=1:00>`__ (with
``unique_field`` defined). The
`examples <https://github.com/kenjyco/redis-helper/tree/master/examples>`__
they reference are **short** and **easy to read**.

The `redis-helper project <https://github.com/kenjyco/redis-helper>`__
evolved from a `reference Python
project <https://github.com/kenjyco/beu/tree/4aea6146fc5f01df3e344b9fadddf28b795dac89>`__
that would be **easy to teach** and follow many practical best practices
and useful patterns. Main purpose was to have something that was super
**easy to configure** (a single ``~/.config/redis-helper/settings.ini``
file for multiple application environments) that did cool things with
`Redis <http://redis.io/topics/data-types-intro>`__.

The `redis-helper package <https://pypi.python.org/pypi/redis-helper>`__
provides a ``Collection`` class that was designed to be **easy to
interact with** in the shell (for exploration, experimentation, and
debugging). Most methods on a ``Collection`` help **minimize typing**
(passing multiple arguments in a single delimited string when
appropriate) and do “the most reasonable thing” whenever possible.

The first time that ``redis_helper`` is imported, the sample
`settings.ini <https://github.com/kenjyco/redis-helper/blob/master/redis_helper/settings.ini>`__
file will be copied to the ``~/.config/redis-helper`` directory.

::

   [default]
   image_version = 6-alpine

   [dev]
   container_name = redis-helper
   port = 6379
   rm = False
   redis_url = redis://localhost:6379/1

   [test]
   container_name = redis-helper-test
   port = 6380
   rm = True
   redis_url = redis://localhost:6380/9

If docker is installed to your system and your user has permission to
use it, the `bg-helper docker
tools <https://github.com/kenjyco/bg-helper#helper-functions-in-bg_helpertools-that-use-docker-if-it-is-installed>`__
will be used to start a redis container for development or running
tests, if Redis is not already installed locally.

(Optionally) install Redis and start server locally
---------------------------------------------------

::

   % sudo apt-get install -y redis-server

   or

   % brew install redis
   % brew services start redis

Install redis-helper
--------------------

-  install latest tag/release of `redis-helper
   package <https://pypi.python.org/pypi/redis-helper>`__

   ::

      % pip3 install redis-helper

-  or, install latest commit on master of `redis-helper
   project <https://github.com/kenjyco/redis-helper>`__

   ::

      % pip3 install git+git://github.com/kenjyco/redis-helper

Intro
-----

`Redis <http://redis.io/topics/data-types-intro>`__ is a fast in-memory
**data structure server**, where each stored object is referenced by a
key name. Objects in Redis correspond to one of several basic types,
each having their own set of specialized commands to perform operations.
The `redis Python package <https://github.com/andymccurdy/redis-py>`__
provides the
`StrictRedis <https://redis-py.readthedocs.org/en/latest/#redis.StrictRedis>`__
class, which contains methods that correspond to all of the Redis server
commands.

When initializing Collection objects, you must specify the “namespace”
and “name” of the collection (which are used to create the internally
used ``_base_key`` property). All Redis keys associated with a
Collection will have a name pattern that starts with the ``_base_key``.

.. code:: python

   import redis_helper as rh


   request_logs = rh.Collection(
       'log',
       'request',
       index_fields='status, uri, host',
       json_fields='request, response, headers'
   )

   urls = rh.Collection(
       'web',
       'url',
       unique_field='name',
       index_fields='domain, _type'
   )

   notes = rh.Collection(
       'input',
       'note',
       index_fields='topic, tag',
       insert_ts=True
   )

   sample = rh.Collection(
       'ns',
       'sample',
       unique_field='name',
       index_fields='status',
       json_fields='data',
       rx_name='\S{4,6}',
       rx_status='(active|inactive|cancelled)',
       rx_aws='[a-z]+\-[0-9a-f]+',
       insert_ts=True
   )

   uses_sample = rh.Collection(
       'ns',
       'uses_sample',
       index_fields='z',
       rx_thing='\S{4,6}',
       reference_fields='thing--ns:sample'
   )

-  a ``unique_field`` can be specified on a collection if items in the
   collection should not contain duplicate values for that particular
   field

   -  the ``unique_field`` cannot also be included in ``json_fields`` or
      ``pickle_fields``
   -  if you specify a ``unique_field``, that field must exist on each
      item you add to the collection

-  use ``index_fields`` to specify which fields you will want to filter
   on when using the ``find`` method

   -  the values for data fields being indexed MUST be simple strings or
      numbers
   -  the values for data fields being indexed SHOULD NOT be long
      strings, as the values themselves are part of the index keys

-  use ``json_fields`` to specify which fields should be JSON encoded
   before insertion to Redis (using the very fast
   `ujson <https://pypi.python.org/pypi/ujson>`__ library)
-  use ``rx_{field}`` to specify a regular expression for any field with
   strict rules for validation
-  use ``reference_fields`` to specify fields that reference the
   ``unique_field`` of another collection

   -  uses field–basekey combos

-  use ``pickle_fields`` to specify which fields should be pickled
   before insertion to Redis
-  set ``insert_ts=True`` to create an additional index to store insert
   times

   -  only do this if you are storing items that you are likely to
      update and also likely to want to know the original insert time

      -  each time an object is updated, the score associated with the
         ``hash_id`` (at the ``_ts_zset_key``) is updated to the current
         timestamp
      -  the score associated with the ``hash_id`` (at the
         ``_in_zset_key``) is never updated

Essentially, you can store a Python
`dict <https://docs.python.org/3/tutorial/datastructures.html#dictionaries>`__
in a Redis `hash <https://redis.io/topics/data-types#hashes>`__ and
index some of the fields in Redis
`sets <https://redis.io/topics/data-types#sets>`__. The collection’s
``_ts_zset_key`` is the Redis key name for the `sorted
set <https://redis.io/topics/data-types#sorted-sets>`__ containing the
``hash_id`` of every hash in the collection (with the ``score`` being a
``utc_float`` corresponding to the UTC time the ``hash_id`` was added or
modified).

-  if ``insert_ts=True`` was passed in when initializing the
   ``Collection`` (or sub-class), then the collection will also define
   ``self.in_zset_key`` to be the Redis key name for the sorted set (for
   ``hash_id`` and ``utc_float`` of insert time)

.. code:: python

   request_logs.add(
       method='get',
       status=400,
       host='blah.net',
       uri='/info',
       request={'x': 50, 'y': 100},
       response={'error': 'bad request'},
   )

   urls.add(
       name='redis-helper github',
       url='https://github.com/kenjyco/redis-helper',
       domain='github.com',
       _type='repo',
   )

The ``get`` method is a wrapper to `hash
commands <http://redis.io/commands#hash>`__ ``hget``, ``hmget``, or
``hgetall``. The actual hash command that gets called is determined by
the number of fields requested.

-  a Python dict is typically returned from ``get``
-  if ``item_format`` is specified, a string will be returned matching
   that format instead

.. code:: python

   request_logs.get('log:request:1')
   request_logs.get('log:request:1', 'host,status')
   request_logs.get('log:request:1', item_format='{status} for {host}{uri}')
   request_logs.get_by_position(0, item_format='{status} for {host}{uri}')
   urls.get_by_position(-1, 'domain,url')
   urls.get_by_unique_value('redis-helper github', item_format='{url} points to a {_type}')

-  the ``get_by_position`` and ``get_by_unique_value`` methods are
   wrappers to ``get``

   -  the ``get_by_unique_value`` method is only useful if a
      ``unique_field`` was set on the Collection

The ``find`` method allows you to return data for items in the
collection that match some set of search criteria. Multiple search terms
(i.e. ``index_field:value`` pairs) maybe be passed in the ``terms``
parameter, as long as they are separated by one of ``,`` ``;`` ``|``.
Any fields specified in the ``get_fields`` parameter are passed along to
the ``get`` method (when the actual fetching takes place).

-  when using ``terms``, all terms that include the same field will be
   treatead like an “or” (union of related sets), then the intersection
   of different sets will be computed
-  see the Redis `set commands <https://redis.io/commands#set>`__ and
   `sorted set commands <https://redis.io/commands#sorted_set>`__

There are many options for specifying time ranges in the ``find`` method
including:

-  ``since`` and ``until`` when specifying ``num:unit`` strings
   (i.e. 15:seconds, 1.5:weeks, etc)
-  ``start_ts`` and ``end_ts`` when specifying timestamps with a form
   between ``YYYY`` and ``YYYY-MM-DD HH:MM:SS.f``
-  ``start`` and ``end`` when specifying a ``utc_float``
-  for ``since``, ``until``, ``start_ts``, and ``end_ts``, multiple
   values may be passed in the string, as long as they are separated by
   one of ``,`` ``;`` ``|``.

   -  when multiple time ranges are specified, the ``find`` method will
      determine all reasonable combinations and return a result-set per
      combination (instead of returning a list of items, returns a dict
      of list of items)

If ``count=True`` is specified, the number of results matching the
search criteria are returned instead of the actual results

-  if there are multiple time ranges specified, counts will be returned
   for each combination

.. code:: python

   request_logs.find('status:400, host:blah.net', get_fields='uri,error')
   request_logs.find(since='1:hr, 30:min', until='15:min, 5:min')
   request_logs.find(count=True, since='1:hr, 30:min', until='15:min, 5:min')
   urls.find(count=True, since='1:hr, 30:min, 10:min, 5:min, 1:min')
   urls.find(start_ts='2017-02-03', end_ts='2017-02-03 7:15:00')
   urls.find(start_ts='2017-02-03', item_format='{_ts} -> {_id}')

The ``update`` method allows you to change values for some fields
(modifying the ``unique_field``, when it is specified, is not allowed).

-  every time a field is modified for a particular ``hash_id``, the
   previous value and score (timestamp) are stored in a Redis hash
-  the ``old_data_for_hash_id`` or ``old_data_for_unique_value`` methods
   can be used to retrieve the history of all changes for a ``hash_id``

.. code:: python

   urls.update('web:url:1', _type='fancy', notes='this is a fancy url')
   urls.old_data_for_hash_id('web:url:1')
   urls.old_data_for_unique_value('redis-helper github')

The ``load_ref_data`` option on ``get``, ``get_by_unique_value``, or
``find`` methods allow you to load the referenced data object from the
other collection (where ``reference_fields`` are specified)

.. code:: python

   In [1]: sample.add(name='hello', aws='ami-0ad5743816d822b81', status='active')
   Out[1]: 'ns:sample:1'

   In [2]: uses_sample.add(thing='hello', z=500, y=True)
   Out[2]: 'ns:uses_sample:1'

   In [3]: uses_sample.get('ns:uses_sample:1')
   Out[3]: {'thing': 'hello', 'z': 500, 'y': True}

   In [4]: uses_sample.get('ns:uses_sample:1', load_ref_data=True)
   Out[4]:
   {'thing': {'name': 'hello',
     'aws': 'ami-0ad5743816d822b81',
     'status': 'active',
     '_id': 'ns:sample:1',
     '_ts': 20201028210044.875},
    'z': 500,
    'y': True}

   In [5]: uses_sample.add(thing='byebye', z=100, y=True)
   Out[5]: 'ns:uses_sample:2'

   In [6]: uses_sample.get('ns:uses_sample:2', load_ref_data=True)
   Out[6]: {'thing': 'byebye', 'z': 100, 'y': True}

Tip
---

There may be times where you want to use redis-helper (if it’s already
installed), but don’t want to make it an explicit requirement of your
project. In cases like this you can do the following:

::

   try:
       import redis_helper as rh
       from redis import ConnectionError as RedisConnectionError
   except (ImportError, ModuleNotFoundError):
       SomeCollection = None
   else:
       try:
           SomeCollection = rh.Collection(
               ...
           )
       except RedisConnectionError:
           SomeCollection = None

Then in whatever function, you can just do:

::

   def some_func():
       if SomeCollection is None:
           return

       # Do stuff with SomeCollection

Local development setup
-----------------------

::

   % git clone https://github.com/kenjyco/redis-helper
   % cd redis-helper
   % ./dev-setup.bash

The
`dev-setup.bash <https://github.com/kenjyco/redis-helper/blob/master/dev-setup.bash>`__
script will create a virtual environment in the ``./venv`` directory
with extra dependencies (ipython, pdbpp, pytest), then copy
``settings.ini`` to the ``~/.config/redis-helper`` directory.

Running tests in development setup
----------------------------------

The
`setup.cfg <https://github.com/kenjyco/redis-helper/blob/master/setup.cfg>`__
file contains the options for ``py.test``, currently ``-vsx -rs --pdb``.

The ``-vsx -rs --pdb`` options will run tests in a verbose manner and
output the reason why tests were skipped (if any were skipped). If there
are any failing tests, ``py.test`` will stop on the first failure and
drop you into a `pdb++ <https://pypi.python.org/pypi/pdbpp/>`__ debugger
session.

See the `debugging
section <https://github.com/kenjyco/redis-helper#settings-environments-testing-and-debugging>`__
of the README for tips on using the debugger and setting breakpoints (in
the actual `project
code <https://github.com/kenjyco/redis-helper/tree/master/redis_helper>`__,
or in the `test
code <https://github.com/kenjyco/redis-helper/tree/master/tests>`__).

::

   % venv/bin/py.test

or

::

   % venv/bin/python3 setup.py test

..

   Note: This option requires ``setuptools`` to be installed.

Usage
-----

The ``rh-download-examples``, ``rh-download-scripts``, ``rh-notes``, and
``rh-shell`` scripts are provided.

::

   $ venv/bin/rh-download-examples --help
   Usage: rh-download-examples [OPTIONS] [DIRECTORY]

     Download redis-helper example files from github

   Options:
     --help  Show this message and exit.

   $ venv/bin/rh-download-scripts --help
   Usage: rh-download-scripts [OPTIONS] [DIRECTORY]

     Download redis-helper script files from github

   Options:
     --help  Show this message and exit.

   $ venv/bin/rh-notes --help
   Usage: rh-notes [OPTIONS] [TOPIC]

     Prompt user to enter notes (about a topic) until finished; or review notes

   Options:
     -c, --ch TEXT  string appended to the topic (default "> ")
     -s, --shell    Start an ipython shell to inspect the notes collection
     --help         Show this message and exit.

   $ venv/bin/rh-shell --help
   Usage: rh-shell [OPTIONS]

     Interactively select a Collection model and start ipython shell

   Options:
     --help  Show this message and exit.

.. code:: python

   >>> import redis_helper as rh
   >>> collection = rh.Collection(..., index_fields='field1, field3')
   >>> hash_id = collection.add(field1='', field2='', field3='', ...)
   >>> collection.add(...)
   >>> collection.add(...)
   >>> collection.update(hash_id, field1='', field4='', ...)
   >>> change_history = collection.old_data_for_hash_id(hash_id)
   >>> data = collection.get(hash_id)
   >>> some_data = collection.get(hash_id, 'field1, field3')
   >>> results = collection.find(...)
   >>> results2 = collection.find('field1:val, field3:val', ...)
   >>> results3 = collection.find(..., get_fields='field2, field4')
   >>> counts = collection.find(count=True, ...)
   >>> top_indexed = collection.index_field_info()
   >>> collection.delete(hash_id, ...)

Basics - Part 1 (request logging demo)
--------------------------------------

`Demo <https://asciinema.org/a/101422?t=1:10>`__ bookmarks:

-  `1:10 <https://asciinema.org/a/101422?t=1:10>`__ is when the
   ``ipython`` session is started with
   ``venv/bin/ipython -i request_logs.py``
-  `3:14 <https://asciinema.org/a/101422?t=3:14>`__ is when a second
   ``ipython`` session is started (in a separate tmux pane) to simulate
   a steady stream of requests with
   ``slow_trickle_requests(randomsleep=True, show=True)``
-  `4:22 <https://asciinema.org/a/101422?t=4:22>`__ is when the
   ``index_field_info`` method is used to get the latest counts of top
   indexed items
-  `6:11 <https://asciinema.org/a/101422?t=6:11>`__ is when
   ``slow_trickle_requests(.001)`` is run to simulate a large quick
   burst in traffic
-  `7:00 <https://asciinema.org/a/101422?t=7:00>`__ is when multiple
   values are passed in the ``since`` argument of ``find``\ …
   ``request_logs.find(count=True, since='5:min, 1:min, 30:sec')``
-  `8:37 <https://asciinema.org/a/101422?t=8:37>`__ is when ``get`` and
   ``get_by_position`` methods are used with a variety of arguments to
   change the structure of what’s returned
-  `10:33 <https://asciinema.org/a/101422?t=10:33>`__ is when the
   ``redis_helper.ADMIN_TIMEZONE`` is changed at run time from
   ``America/Chicago`` to ``Europe/London``
-  `11:27 <https://asciinema.org/a/101422?t=11:27>`__ is when ``find``
   is used with a variety of arguments to change the structure of what’s
   returned
-  `14:30 <https://asciinema.org/a/101422?t=14:30>`__ is when ``find``
   is used with multiple search terms and multiple ``since`` values…
   ``request_logs.find('host:dogs.com, uri:/breeds', count=True, since='5:min, 1:min, 10:sec')``
-  `15:54 <https://asciinema.org/a/101422?t=15:54>`__ is when the
   ``update`` method is used to modify data and change history is
   retrieved with the ``old_data_for_hash_id`` method

The first demo walks through the following:

-  creating a virtual environment, installing redis-helper, and
   downloading example files

   ::

      $ python3 -m venv venv
      $ venv/bin/pip3 install redis-helper ipython
      $ venv/bin/rh-download-examples
      $ cat ~/.config/redis-helper/settings.ini
      $ venv/bin/ipython -i request_logs.py

-  using the sample ``Collection`` defined in
   `request_logs.py <https://github.com/kenjyco/redis-helper/blob/master/examples/request_logs.py>`__
   to

   -  show values of some properties on a ``Collection``

      -  ``redis_helper.Collection._base_key``
      -  ``redis_helper.Collection.now_pretty``
      -  ``redis_helper.Collection.now_utc_float``
      -  ``redis_helper.Collection.keyspace``
      -  ``redis_helper.Collection.size``
      -  ``redis_helper.Collection.first``
      -  ``redis_helper.Collection.last``

   -  show values of some settings from ``redis_helper``

      -  ``redis_helper.APP_ENV``
      -  ``redis_helper.REDIS_URL``
      -  ``redis_helper.REDIS``
      -  ``redis_helper.SETTINGS_FILE``
      -  ``redis_helper.ADMIN_TIMEZONE``

   -  show output from some methods on a ``Collection``

      -  ``redis_helper.Collection.index_field_info()``
      -  ``redis_helper.Collection.find()``
      -  ``redis_helper.Collection.find(count=True)``
      -  ``redis_helper.Collection.find(count=True, since='30:sec')``
      -  ``redis_helper.Collection.find(since='30:sec')``
      -  ``redis_helper.Collection.find(since='30:sec', admin_fmt=True)``
      -  ``redis_helper.Collection.find(count=True, since='5:min, 1:min, 30:sec')``
      -  ``redis_helper.Collection.find('index_field:value')``
      -  ``redis_helper.Collection.find('index_field:value', all_fields=True, limit=2)``
      -  ``redis_helper.Collection.find('index_field:value', all_fields=True, limit=2, admin_fmt=True, item_format='{_ts} -> {_id}')``
      -  ``redis_helper.Collection.find('index_field:value', get_fields='field1, field2', include_meta=False)``
      -  ``redis_helper.Collection.find('index_field1:value1, index_field2:value2', count=True)``
      -  ``redis_helper.Collection.find('index_field1:value1, index_field2:value2', count=True, since='5:min, 1:min, 10:sec')``
      -  ``redis_helper.Collection.get(hash_id)``
      -  ``redis_helper.Collection.get(hash_id, 'field1,field2,field3')``
      -  ``redis_helper.Collection.get(hash_id, include_meta=True)``
      -  ``redis_helper.Collection.get(hash_id, include_meta=True, fields='field1, field2')``
      -  ``redis_helper.Collection.get(hash_id, include_meta=True, item_format='{_ts} -> {_id}')``
      -  ``redis_helper.Collection.get_by_position(0)``
      -  ``redis_helper.Collection.get_by_position(0, include_meta=True, admin_fmt=True)``
      -  ``redis_helper.Collection.update(hash_id, field1='value1', field2='value2')``
      -  ``redis_helper.Collection.old_data_for_hash_id(hash_id)``

Basics - Part 2 (urls demo, with unique field)
----------------------------------------------

`Demo <https://asciinema.org/a/75kl95ty9vg2jl93pfz9fbs9q?t=1:00>`__
bookmarks:

-  ``TODO``

The second demo walks through the following:

-  using the sample ``Collection`` defined in
   `urls.py <https://github.com/kenjyco/redis-helper/blob/master/examples/urls.py>`__
   to

   -  ``TODO``

Settings, environments, testing, and debugging
----------------------------------------------

To trigger a debugger session at a specific place in the `project
code <https://github.com/kenjyco/redis-helper/tree/master/redis_helper>`__,
insert the following, one line above where you want to inspect

::

   import pdb; pdb.set_trace()

To start the debugger inside `test
code <https://github.com/kenjyco/redis-helper/tree/master/tests>`__, use

::

   pytest.set_trace()

-  use ``(l)ist`` to list context lines
-  use ``(n)ext`` to move on to the next statement
-  use ``(s)tep`` to step into a function
-  use ``(c)ontinue`` to continue to next break point
   (i.e. ``set_trace()`` lines in your code)
-  use ``sticky`` to toggle sticky mode (to constantly show the
   currently executing code as you move through with the debugger)
-  use ``pp`` to pretty print a variable or statement

If the redis server at ``redis_url`` (in the **test section** of
``~/.config/redis-server/settings.ini``) is not running or is not empty,
redis server tests will be skipped.

Use the ``APP_ENV`` environment variable to specify which section of the
``settings.ini`` file your settings will be loaded from. Any settings in
the ``default`` section can be overwritten if explicity set in another
section.

-  if no ``APP_ENV`` is explicitly set, ``dev`` is assumed
-  the ``APP_ENV`` setting is overwritten to be ``test`` no matter what
   was set when calling ``py.test`` tests

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/kenjyco/redis-helper",
    "name": "redis-helper",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "redis,cli,command-line,dictionary,data,database,secondary index,model,prototype,event logging,dashboard,easy modeling,helper,kenjyco",
    "author": "Ken",
    "author_email": "kenjyco@gmail.com",
    "download_url": "https://github.com/kenjyco/redis-helper/tarball/v0.4.8",
    "platform": null,
    "description": "   Note: as of redis-helper v0.4.0, version 3.0 of redis-py is in use,\n   which has backwards incompatible changes withe redis-py 2.x. See\n   https://github.com/redis/redis-py/tree/70ef9ec68f9163c86d4cace2941e2f0ae4ce8525#upgrading-from-redis-py-2x-to-30\n\nAbout\n-----\n\nInstall redis-helper, create an instance of ``redis_helper.Collection``\n(**the args/kwargs define the model**) and use the ``add``, ``get``,\n``update``, ``delete``, and ``find`` methods to:\n\n-  quickly store/retrieve/modify Python dicts in Redis\n-  filter through indexed fields with simple/flexible find arguments\n-  power real-time dashboards with metrics at a variety of time ranges\n-  super-charge event logging and system debugging\n-  build FAST prototypes and simulators\n-  greatly simplify data access patterns throughout application\n\nSee the `request logging demo <https://asciinema.org/a/101422?t=1:10>`__\nand `urls\ndemo <https://asciinema.org/a/75kl95ty9vg2jl93pfz9fbs9q?t=1:00>`__ (with\n``unique_field`` defined). The\n`examples <https://github.com/kenjyco/redis-helper/tree/master/examples>`__\nthey reference are **short** and **easy to read**.\n\nThe `redis-helper project <https://github.com/kenjyco/redis-helper>`__\nevolved from a `reference Python\nproject <https://github.com/kenjyco/beu/tree/4aea6146fc5f01df3e344b9fadddf28b795dac89>`__\nthat would be **easy to teach** and follow many practical best practices\nand useful patterns. Main purpose was to have something that was super\n**easy to configure** (a single ``~/.config/redis-helper/settings.ini``\nfile for multiple application environments) that did cool things with\n`Redis <http://redis.io/topics/data-types-intro>`__.\n\nThe `redis-helper package <https://pypi.python.org/pypi/redis-helper>`__\nprovides a ``Collection`` class that was designed to be **easy to\ninteract with** in the shell (for exploration, experimentation, and\ndebugging). Most methods on a ``Collection`` help **minimize typing**\n(passing multiple arguments in a single delimited string when\nappropriate) and do \u201cthe most reasonable thing\u201d whenever possible.\n\nThe first time that ``redis_helper`` is imported, the sample\n`settings.ini <https://github.com/kenjyco/redis-helper/blob/master/redis_helper/settings.ini>`__\nfile will be copied to the ``~/.config/redis-helper`` directory.\n\n::\n\n   [default]\n   image_version = 6-alpine\n\n   [dev]\n   container_name = redis-helper\n   port = 6379\n   rm = False\n   redis_url = redis://localhost:6379/1\n\n   [test]\n   container_name = redis-helper-test\n   port = 6380\n   rm = True\n   redis_url = redis://localhost:6380/9\n\nIf docker is installed to your system and your user has permission to\nuse it, the `bg-helper docker\ntools <https://github.com/kenjyco/bg-helper#helper-functions-in-bg_helpertools-that-use-docker-if-it-is-installed>`__\nwill be used to start a redis container for development or running\ntests, if Redis is not already installed locally.\n\n(Optionally) install Redis and start server locally\n---------------------------------------------------\n\n::\n\n   % sudo apt-get install -y redis-server\n\n   or\n\n   % brew install redis\n   % brew services start redis\n\nInstall redis-helper\n--------------------\n\n-  install latest tag/release of `redis-helper\n   package <https://pypi.python.org/pypi/redis-helper>`__\n\n   ::\n\n      % pip3 install redis-helper\n\n-  or, install latest commit on master of `redis-helper\n   project <https://github.com/kenjyco/redis-helper>`__\n\n   ::\n\n      % pip3 install git+git://github.com/kenjyco/redis-helper\n\nIntro\n-----\n\n`Redis <http://redis.io/topics/data-types-intro>`__ is a fast in-memory\n**data structure server**, where each stored object is referenced by a\nkey name. Objects in Redis correspond to one of several basic types,\neach having their own set of specialized commands to perform operations.\nThe `redis Python package <https://github.com/andymccurdy/redis-py>`__\nprovides the\n`StrictRedis <https://redis-py.readthedocs.org/en/latest/#redis.StrictRedis>`__\nclass, which contains methods that correspond to all of the Redis server\ncommands.\n\nWhen initializing Collection objects, you must specify the \u201cnamespace\u201d\nand \u201cname\u201d of the collection (which are used to create the internally\nused ``_base_key`` property). All Redis keys associated with a\nCollection will have a name pattern that starts with the ``_base_key``.\n\n.. code:: python\n\n   import redis_helper as rh\n\n\n   request_logs = rh.Collection(\n       'log',\n       'request',\n       index_fields='status, uri, host',\n       json_fields='request, response, headers'\n   )\n\n   urls = rh.Collection(\n       'web',\n       'url',\n       unique_field='name',\n       index_fields='domain, _type'\n   )\n\n   notes = rh.Collection(\n       'input',\n       'note',\n       index_fields='topic, tag',\n       insert_ts=True\n   )\n\n   sample = rh.Collection(\n       'ns',\n       'sample',\n       unique_field='name',\n       index_fields='status',\n       json_fields='data',\n       rx_name='\\S{4,6}',\n       rx_status='(active|inactive|cancelled)',\n       rx_aws='[a-z]+\\-[0-9a-f]+',\n       insert_ts=True\n   )\n\n   uses_sample = rh.Collection(\n       'ns',\n       'uses_sample',\n       index_fields='z',\n       rx_thing='\\S{4,6}',\n       reference_fields='thing--ns:sample'\n   )\n\n-  a ``unique_field`` can be specified on a collection if items in the\n   collection should not contain duplicate values for that particular\n   field\n\n   -  the ``unique_field`` cannot also be included in ``json_fields`` or\n      ``pickle_fields``\n   -  if you specify a ``unique_field``, that field must exist on each\n      item you add to the collection\n\n-  use ``index_fields`` to specify which fields you will want to filter\n   on when using the ``find`` method\n\n   -  the values for data fields being indexed MUST be simple strings or\n      numbers\n   -  the values for data fields being indexed SHOULD NOT be long\n      strings, as the values themselves are part of the index keys\n\n-  use ``json_fields`` to specify which fields should be JSON encoded\n   before insertion to Redis (using the very fast\n   `ujson <https://pypi.python.org/pypi/ujson>`__ library)\n-  use ``rx_{field}`` to specify a regular expression for any field with\n   strict rules for validation\n-  use ``reference_fields`` to specify fields that reference the\n   ``unique_field`` of another collection\n\n   -  uses field\u2013basekey combos\n\n-  use ``pickle_fields`` to specify which fields should be pickled\n   before insertion to Redis\n-  set ``insert_ts=True`` to create an additional index to store insert\n   times\n\n   -  only do this if you are storing items that you are likely to\n      update and also likely to want to know the original insert time\n\n      -  each time an object is updated, the score associated with the\n         ``hash_id`` (at the ``_ts_zset_key``) is updated to the current\n         timestamp\n      -  the score associated with the ``hash_id`` (at the\n         ``_in_zset_key``) is never updated\n\nEssentially, you can store a Python\n`dict <https://docs.python.org/3/tutorial/datastructures.html#dictionaries>`__\nin a Redis `hash <https://redis.io/topics/data-types#hashes>`__ and\nindex some of the fields in Redis\n`sets <https://redis.io/topics/data-types#sets>`__. The collection\u2019s\n``_ts_zset_key`` is the Redis key name for the `sorted\nset <https://redis.io/topics/data-types#sorted-sets>`__ containing the\n``hash_id`` of every hash in the collection (with the ``score`` being a\n``utc_float`` corresponding to the UTC time the ``hash_id`` was added or\nmodified).\n\n-  if ``insert_ts=True`` was passed in when initializing the\n   ``Collection`` (or sub-class), then the collection will also define\n   ``self.in_zset_key`` to be the Redis key name for the sorted set (for\n   ``hash_id`` and ``utc_float`` of insert time)\n\n.. code:: python\n\n   request_logs.add(\n       method='get',\n       status=400,\n       host='blah.net',\n       uri='/info',\n       request={'x': 50, 'y': 100},\n       response={'error': 'bad request'},\n   )\n\n   urls.add(\n       name='redis-helper github',\n       url='https://github.com/kenjyco/redis-helper',\n       domain='github.com',\n       _type='repo',\n   )\n\nThe ``get`` method is a wrapper to `hash\ncommands <http://redis.io/commands#hash>`__ ``hget``, ``hmget``, or\n``hgetall``. The actual hash command that gets called is determined by\nthe number of fields requested.\n\n-  a Python dict is typically returned from ``get``\n-  if ``item_format`` is specified, a string will be returned matching\n   that format instead\n\n.. code:: python\n\n   request_logs.get('log:request:1')\n   request_logs.get('log:request:1', 'host,status')\n   request_logs.get('log:request:1', item_format='{status} for {host}{uri}')\n   request_logs.get_by_position(0, item_format='{status} for {host}{uri}')\n   urls.get_by_position(-1, 'domain,url')\n   urls.get_by_unique_value('redis-helper github', item_format='{url} points to a {_type}')\n\n-  the ``get_by_position`` and ``get_by_unique_value`` methods are\n   wrappers to ``get``\n\n   -  the ``get_by_unique_value`` method is only useful if a\n      ``unique_field`` was set on the Collection\n\nThe ``find`` method allows you to return data for items in the\ncollection that match some set of search criteria. Multiple search terms\n(i.e. ``index_field:value`` pairs) maybe be passed in the ``terms``\nparameter, as long as they are separated by one of ``,`` ``;`` ``|``.\nAny fields specified in the ``get_fields`` parameter are passed along to\nthe ``get`` method (when the actual fetching takes place).\n\n-  when using ``terms``, all terms that include the same field will be\n   treatead like an \u201cor\u201d (union of related sets), then the intersection\n   of different sets will be computed\n-  see the Redis `set commands <https://redis.io/commands#set>`__ and\n   `sorted set commands <https://redis.io/commands#sorted_set>`__\n\nThere are many options for specifying time ranges in the ``find`` method\nincluding:\n\n-  ``since`` and ``until`` when specifying ``num:unit`` strings\n   (i.e.\u00a015:seconds, 1.5:weeks, etc)\n-  ``start_ts`` and ``end_ts`` when specifying timestamps with a form\n   between ``YYYY`` and ``YYYY-MM-DD HH:MM:SS.f``\n-  ``start`` and ``end`` when specifying a ``utc_float``\n-  for ``since``, ``until``, ``start_ts``, and ``end_ts``, multiple\n   values may be passed in the string, as long as they are separated by\n   one of ``,`` ``;`` ``|``.\n\n   -  when multiple time ranges are specified, the ``find`` method will\n      determine all reasonable combinations and return a result-set per\n      combination (instead of returning a list of items, returns a dict\n      of list of items)\n\nIf ``count=True`` is specified, the number of results matching the\nsearch criteria are returned instead of the actual results\n\n-  if there are multiple time ranges specified, counts will be returned\n   for each combination\n\n.. code:: python\n\n   request_logs.find('status:400, host:blah.net', get_fields='uri,error')\n   request_logs.find(since='1:hr, 30:min', until='15:min, 5:min')\n   request_logs.find(count=True, since='1:hr, 30:min', until='15:min, 5:min')\n   urls.find(count=True, since='1:hr, 30:min, 10:min, 5:min, 1:min')\n   urls.find(start_ts='2017-02-03', end_ts='2017-02-03 7:15:00')\n   urls.find(start_ts='2017-02-03', item_format='{_ts} -> {_id}')\n\nThe ``update`` method allows you to change values for some fields\n(modifying the ``unique_field``, when it is specified, is not allowed).\n\n-  every time a field is modified for a particular ``hash_id``, the\n   previous value and score (timestamp) are stored in a Redis hash\n-  the ``old_data_for_hash_id`` or ``old_data_for_unique_value`` methods\n   can be used to retrieve the history of all changes for a ``hash_id``\n\n.. code:: python\n\n   urls.update('web:url:1', _type='fancy', notes='this is a fancy url')\n   urls.old_data_for_hash_id('web:url:1')\n   urls.old_data_for_unique_value('redis-helper github')\n\nThe ``load_ref_data`` option on ``get``, ``get_by_unique_value``, or\n``find`` methods allow you to load the referenced data object from the\nother collection (where ``reference_fields`` are specified)\n\n.. code:: python\n\n   In [1]: sample.add(name='hello', aws='ami-0ad5743816d822b81', status='active')\n   Out[1]: 'ns:sample:1'\n\n   In [2]: uses_sample.add(thing='hello', z=500, y=True)\n   Out[2]: 'ns:uses_sample:1'\n\n   In [3]: uses_sample.get('ns:uses_sample:1')\n   Out[3]: {'thing': 'hello', 'z': 500, 'y': True}\n\n   In [4]: uses_sample.get('ns:uses_sample:1', load_ref_data=True)\n   Out[4]:\n   {'thing': {'name': 'hello',\n     'aws': 'ami-0ad5743816d822b81',\n     'status': 'active',\n     '_id': 'ns:sample:1',\n     '_ts': 20201028210044.875},\n    'z': 500,\n    'y': True}\n\n   In [5]: uses_sample.add(thing='byebye', z=100, y=True)\n   Out[5]: 'ns:uses_sample:2'\n\n   In [6]: uses_sample.get('ns:uses_sample:2', load_ref_data=True)\n   Out[6]: {'thing': 'byebye', 'z': 100, 'y': True}\n\nTip\n---\n\nThere may be times where you want to use redis-helper (if it\u2019s already\ninstalled), but don\u2019t want to make it an explicit requirement of your\nproject. In cases like this you can do the following:\n\n::\n\n   try:\n       import redis_helper as rh\n       from redis import ConnectionError as RedisConnectionError\n   except (ImportError, ModuleNotFoundError):\n       SomeCollection = None\n   else:\n       try:\n           SomeCollection = rh.Collection(\n               ...\n           )\n       except RedisConnectionError:\n           SomeCollection = None\n\nThen in whatever function, you can just do:\n\n::\n\n   def some_func():\n       if SomeCollection is None:\n           return\n\n       # Do stuff with SomeCollection\n\nLocal development setup\n-----------------------\n\n::\n\n   % git clone https://github.com/kenjyco/redis-helper\n   % cd redis-helper\n   % ./dev-setup.bash\n\nThe\n`dev-setup.bash <https://github.com/kenjyco/redis-helper/blob/master/dev-setup.bash>`__\nscript will create a virtual environment in the ``./venv`` directory\nwith extra dependencies (ipython, pdbpp, pytest), then copy\n``settings.ini`` to the ``~/.config/redis-helper`` directory.\n\nRunning tests in development setup\n----------------------------------\n\nThe\n`setup.cfg <https://github.com/kenjyco/redis-helper/blob/master/setup.cfg>`__\nfile contains the options for ``py.test``, currently ``-vsx -rs --pdb``.\n\nThe ``-vsx -rs --pdb`` options will run tests in a verbose manner and\noutput the reason why tests were skipped (if any were skipped). If there\nare any failing tests, ``py.test`` will stop on the first failure and\ndrop you into a `pdb++ <https://pypi.python.org/pypi/pdbpp/>`__ debugger\nsession.\n\nSee the `debugging\nsection <https://github.com/kenjyco/redis-helper#settings-environments-testing-and-debugging>`__\nof the README for tips on using the debugger and setting breakpoints (in\nthe actual `project\ncode <https://github.com/kenjyco/redis-helper/tree/master/redis_helper>`__,\nor in the `test\ncode <https://github.com/kenjyco/redis-helper/tree/master/tests>`__).\n\n::\n\n   % venv/bin/py.test\n\nor\n\n::\n\n   % venv/bin/python3 setup.py test\n\n..\n\n   Note: This option requires ``setuptools`` to be installed.\n\nUsage\n-----\n\nThe ``rh-download-examples``, ``rh-download-scripts``, ``rh-notes``, and\n``rh-shell`` scripts are provided.\n\n::\n\n   $ venv/bin/rh-download-examples --help\n   Usage: rh-download-examples [OPTIONS] [DIRECTORY]\n\n     Download redis-helper example files from github\n\n   Options:\n     --help  Show this message and exit.\n\n   $ venv/bin/rh-download-scripts --help\n   Usage: rh-download-scripts [OPTIONS] [DIRECTORY]\n\n     Download redis-helper script files from github\n\n   Options:\n     --help  Show this message and exit.\n\n   $ venv/bin/rh-notes --help\n   Usage: rh-notes [OPTIONS] [TOPIC]\n\n     Prompt user to enter notes (about a topic) until finished; or review notes\n\n   Options:\n     -c, --ch TEXT  string appended to the topic (default \"> \")\n     -s, --shell    Start an ipython shell to inspect the notes collection\n     --help         Show this message and exit.\n\n   $ venv/bin/rh-shell --help\n   Usage: rh-shell [OPTIONS]\n\n     Interactively select a Collection model and start ipython shell\n\n   Options:\n     --help  Show this message and exit.\n\n.. code:: python\n\n   >>> import redis_helper as rh\n   >>> collection = rh.Collection(..., index_fields='field1, field3')\n   >>> hash_id = collection.add(field1='', field2='', field3='', ...)\n   >>> collection.add(...)\n   >>> collection.add(...)\n   >>> collection.update(hash_id, field1='', field4='', ...)\n   >>> change_history = collection.old_data_for_hash_id(hash_id)\n   >>> data = collection.get(hash_id)\n   >>> some_data = collection.get(hash_id, 'field1, field3')\n   >>> results = collection.find(...)\n   >>> results2 = collection.find('field1:val, field3:val', ...)\n   >>> results3 = collection.find(..., get_fields='field2, field4')\n   >>> counts = collection.find(count=True, ...)\n   >>> top_indexed = collection.index_field_info()\n   >>> collection.delete(hash_id, ...)\n\nBasics - Part 1 (request logging demo)\n--------------------------------------\n\n`Demo <https://asciinema.org/a/101422?t=1:10>`__ bookmarks:\n\n-  `1:10 <https://asciinema.org/a/101422?t=1:10>`__ is when the\n   ``ipython`` session is started with\n   ``venv/bin/ipython -i request_logs.py``\n-  `3:14 <https://asciinema.org/a/101422?t=3:14>`__ is when a second\n   ``ipython`` session is started (in a separate tmux pane) to simulate\n   a steady stream of requests with\n   ``slow_trickle_requests(randomsleep=True, show=True)``\n-  `4:22 <https://asciinema.org/a/101422?t=4:22>`__ is when the\n   ``index_field_info`` method is used to get the latest counts of top\n   indexed items\n-  `6:11 <https://asciinema.org/a/101422?t=6:11>`__ is when\n   ``slow_trickle_requests(.001)`` is run to simulate a large quick\n   burst in traffic\n-  `7:00 <https://asciinema.org/a/101422?t=7:00>`__ is when multiple\n   values are passed in the ``since`` argument of ``find``\\ \u2026\n   ``request_logs.find(count=True, since='5:min, 1:min, 30:sec')``\n-  `8:37 <https://asciinema.org/a/101422?t=8:37>`__ is when ``get`` and\n   ``get_by_position`` methods are used with a variety of arguments to\n   change the structure of what\u2019s returned\n-  `10:33 <https://asciinema.org/a/101422?t=10:33>`__ is when the\n   ``redis_helper.ADMIN_TIMEZONE`` is changed at run time from\n   ``America/Chicago`` to ``Europe/London``\n-  `11:27 <https://asciinema.org/a/101422?t=11:27>`__ is when ``find``\n   is used with a variety of arguments to change the structure of what\u2019s\n   returned\n-  `14:30 <https://asciinema.org/a/101422?t=14:30>`__ is when ``find``\n   is used with multiple search terms and multiple ``since`` values\u2026\n   ``request_logs.find('host:dogs.com, uri:/breeds', count=True, since='5:min, 1:min, 10:sec')``\n-  `15:54 <https://asciinema.org/a/101422?t=15:54>`__ is when the\n   ``update`` method is used to modify data and change history is\n   retrieved with the ``old_data_for_hash_id`` method\n\nThe first demo walks through the following:\n\n-  creating a virtual environment, installing redis-helper, and\n   downloading example files\n\n   ::\n\n      $ python3 -m venv venv\n      $ venv/bin/pip3 install redis-helper ipython\n      $ venv/bin/rh-download-examples\n      $ cat ~/.config/redis-helper/settings.ini\n      $ venv/bin/ipython -i request_logs.py\n\n-  using the sample ``Collection`` defined in\n   `request_logs.py <https://github.com/kenjyco/redis-helper/blob/master/examples/request_logs.py>`__\n   to\n\n   -  show values of some properties on a ``Collection``\n\n      -  ``redis_helper.Collection._base_key``\n      -  ``redis_helper.Collection.now_pretty``\n      -  ``redis_helper.Collection.now_utc_float``\n      -  ``redis_helper.Collection.keyspace``\n      -  ``redis_helper.Collection.size``\n      -  ``redis_helper.Collection.first``\n      -  ``redis_helper.Collection.last``\n\n   -  show values of some settings from ``redis_helper``\n\n      -  ``redis_helper.APP_ENV``\n      -  ``redis_helper.REDIS_URL``\n      -  ``redis_helper.REDIS``\n      -  ``redis_helper.SETTINGS_FILE``\n      -  ``redis_helper.ADMIN_TIMEZONE``\n\n   -  show output from some methods on a ``Collection``\n\n      -  ``redis_helper.Collection.index_field_info()``\n      -  ``redis_helper.Collection.find()``\n      -  ``redis_helper.Collection.find(count=True)``\n      -  ``redis_helper.Collection.find(count=True, since='30:sec')``\n      -  ``redis_helper.Collection.find(since='30:sec')``\n      -  ``redis_helper.Collection.find(since='30:sec', admin_fmt=True)``\n      -  ``redis_helper.Collection.find(count=True, since='5:min, 1:min, 30:sec')``\n      -  ``redis_helper.Collection.find('index_field:value')``\n      -  ``redis_helper.Collection.find('index_field:value', all_fields=True, limit=2)``\n      -  ``redis_helper.Collection.find('index_field:value', all_fields=True, limit=2, admin_fmt=True, item_format='{_ts} -> {_id}')``\n      -  ``redis_helper.Collection.find('index_field:value', get_fields='field1, field2', include_meta=False)``\n      -  ``redis_helper.Collection.find('index_field1:value1, index_field2:value2', count=True)``\n      -  ``redis_helper.Collection.find('index_field1:value1, index_field2:value2', count=True, since='5:min, 1:min, 10:sec')``\n      -  ``redis_helper.Collection.get(hash_id)``\n      -  ``redis_helper.Collection.get(hash_id, 'field1,field2,field3')``\n      -  ``redis_helper.Collection.get(hash_id, include_meta=True)``\n      -  ``redis_helper.Collection.get(hash_id, include_meta=True, fields='field1, field2')``\n      -  ``redis_helper.Collection.get(hash_id, include_meta=True, item_format='{_ts} -> {_id}')``\n      -  ``redis_helper.Collection.get_by_position(0)``\n      -  ``redis_helper.Collection.get_by_position(0, include_meta=True, admin_fmt=True)``\n      -  ``redis_helper.Collection.update(hash_id, field1='value1', field2='value2')``\n      -  ``redis_helper.Collection.old_data_for_hash_id(hash_id)``\n\nBasics - Part 2 (urls demo, with unique field)\n----------------------------------------------\n\n`Demo <https://asciinema.org/a/75kl95ty9vg2jl93pfz9fbs9q?t=1:00>`__\nbookmarks:\n\n-  ``TODO``\n\nThe second demo walks through the following:\n\n-  using the sample ``Collection`` defined in\n   `urls.py <https://github.com/kenjyco/redis-helper/blob/master/examples/urls.py>`__\n   to\n\n   -  ``TODO``\n\nSettings, environments, testing, and debugging\n----------------------------------------------\n\nTo trigger a debugger session at a specific place in the `project\ncode <https://github.com/kenjyco/redis-helper/tree/master/redis_helper>`__,\ninsert the following, one line above where you want to inspect\n\n::\n\n   import pdb; pdb.set_trace()\n\nTo start the debugger inside `test\ncode <https://github.com/kenjyco/redis-helper/tree/master/tests>`__, use\n\n::\n\n   pytest.set_trace()\n\n-  use ``(l)ist`` to list context lines\n-  use ``(n)ext`` to move on to the next statement\n-  use ``(s)tep`` to step into a function\n-  use ``(c)ontinue`` to continue to next break point\n   (i.e.\u00a0``set_trace()`` lines in your code)\n-  use ``sticky`` to toggle sticky mode (to constantly show the\n   currently executing code as you move through with the debugger)\n-  use ``pp`` to pretty print a variable or statement\n\nIf the redis server at ``redis_url`` (in the **test section** of\n``~/.config/redis-server/settings.ini``) is not running or is not empty,\nredis server tests will be skipped.\n\nUse the ``APP_ENV`` environment variable to specify which section of the\n``settings.ini`` file your settings will be loaded from. Any settings in\nthe ``default`` section can be overwritten if explicity set in another\nsection.\n\n-  if no ``APP_ENV`` is explicitly set, ``dev`` is assumed\n-  the ``APP_ENV`` setting is overwritten to be ``test`` no matter what\n   was set when calling ``py.test`` tests\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Easily store, index, and modify Python dicts in Redis (with flexible searching)",
    "version": "0.4.8",
    "project_urls": {
        "Download": "https://github.com/kenjyco/redis-helper/tarball/v0.4.8",
        "Homepage": "https://github.com/kenjyco/redis-helper"
    },
    "split_keywords": [
        "redis",
        "cli",
        "command-line",
        "dictionary",
        "data",
        "database",
        "secondary index",
        "model",
        "prototype",
        "event logging",
        "dashboard",
        "easy modeling",
        "helper",
        "kenjyco"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3ed9c78cac6f91a4d3f1071af7728eee47d9ece8ececd0b85e7fa8247627b069",
                "md5": "729972274e7214b34ed67a74c04baaee",
                "sha256": "63f632f7e8c559e914c38f20867c60d4be8eee9ed55f65d787a61b45edeaed63"
            },
            "downloads": -1,
            "filename": "redis_helper-0.4.8-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "729972274e7214b34ed67a74c04baaee",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 30730,
            "upload_time": "2024-01-11T02:48:25",
            "upload_time_iso_8601": "2024-01-11T02:48:25.847812Z",
            "url": "https://files.pythonhosted.org/packages/3e/d9/c78cac6f91a4d3f1071af7728eee47d9ece8ececd0b85e7fa8247627b069/redis_helper-0.4.8-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-11 02:48:25",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kenjyco",
    "github_project": "redis-helper",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "redis-helper"
}
        
Ken
Elapsed time: 3.29494s