Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
kororo committed May 1, 2020
2 parents af7d116 + e62e809 commit f9cf6c9
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 62 deletions.
2 changes: 2 additions & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
service_name: travis-pro
repo_token: PcETCgtcfiW72CnxnSksmQhmo9nbfgCEr
106 changes: 74 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ Random generated String from regex pattern

# WARNING

The library **rand** is still in working-in-progress. It is subject to high possibility of API changes. Would appreciate for feedbacks, suggestions or helps.
The library **rand** is still in working-in-progress. It is subject to high possibility of API changes. Would appreciate feedback, suggestions or help.

# Why?

There are lot of existing projects similar to **rand**, they are powerful and doing the similar goals and results. However most of them are old projects/non-maintained and non MIT license that not really 100% embracing the idea of OOS.
There are lot of existing projects similar to **rand**, they are powerful and have similar goals and results. However most of them are old projects/non-maintained and non-MIT licenses.

This is good opportunity for **rand** to be the library to help generate random data for any projects and gather all other existing library to be the main driver.
This is a good opportunity for **rand** to be the library to help generate random data for any projects and gather all other existing libraries to be the main driver.


# Install
Expand All @@ -39,53 +39,86 @@ Basic usage **rand** examples
from rand import Rand

# initialise object
rand = Rand()
rnd = Rand()

# generate pattern literal
rand.gen('koro') # ['koro']
rand.gen('28') # ['28']
rand.gen('a-z') # ['a-z']
rnd.gen('koro') # ['koro']
rnd.gen('28') # ['28']
rnd.gen('a-z') # ['a-z']

# generate pattern any
rand.gen('.') # any char in string.printable
rnd.gen('.') # any char in string.printable

# generate pattern branch
rand.gen('ko|ro') # either ['ko'] or ['ro']
rand.gen('ko|ro|ro') # either ['ko'] or ['ro']
rnd.gen('ko|ro') # either ['ko'] or ['ro']
rnd.gen('ko|ro|ro') # either ['ko'] or ['ro']

# generate pattern in
rand.gen('[kororo]') # either ['k'] or ['o'] or ['r']
rand.gen('k[o]r[o]r[o]') # ['kororo']
rnd.gen('[kororo]') # either ['k'] or ['o'] or ['r']
rnd.gen('k[o]r[o]r[o]') # ['kororo']

# generate pattern repeat
rand.gen('r{2,8}') # char r in length between 2 to 8 times
rnd.gen('r{2,8}') # char r in length between 2 to 8 times

# generate pattern range
rand.gen('[a-z]') # char between a to z
rnd.gen('[a-z]') # char between a to z

# generate pattern subpattern
rand.gen('(ro)') # ['ro']
rnd.gen('(ro)') # ['ro']
```

Providers
---------

The library **rand** at core only provide random generator based on regex. Providers are built to allow extensions for rand.
The library **rand** at core only provide random generators based on regex. Providers are built to allow extensions for rand.

## Built-in Providers

There are a few built-in providers inside **rand**

### EN Provider

This library cover most usage around English requirements.
This library covers most usage around English requirements.

```python
from rand import Rand


rand = Rand()
rand.gen('(:en_vocal:)') # char either a, i, u, e, o
rnd = Rand()
rnd.gen('(:en_vocal:)') # char either a, i, u, e, o
```

### Dataset Provider

This library helps on getting data from dataset such as Python object or Database with [peewee](https://github.com/coleifer/peewee).

```python
from rand import Rand
from rand.providers.ds import RandDatasetBaseProvider, ListDatasetTarget


# example using dict of list
db = {'names': ['test1', 'test1'], 'cities': ['test2', 'test2']}
ds = RandDatasetBaseProvider(prefix='ds', target=ListDatasetTarget(db=db))
rnd = Rand()
rnd.register_provider(ds)
rnd.gen('(:ds_get:)', ['names']) # ['test1']
rnd.gen('(:ds_get:)', ['cities']) # ['test2']
# or, magic getattr
rnd.gen('(:ds_get_names:)-(:ds_get_cities:)') # ['test1-test2']

# example of database using peewee
from peewee import Proxy
from playhouse.sqlite_ext import CSqliteExtDatabase
from rand.providers.ds import RandDatasetBaseProvider, DBDatasetTarget
db = Proxy()
# ensure to have table with name "names", contains column at least (id, name)
db.initialize(CSqliteExtDatabase(':memory:', bloomfilter=True))
ds = RandDatasetBaseProvider(prefix='ds', target=DBDatasetTarget(db=db))
rnd = Rand()
rnd.register_provider(ds)
rnd.gen('(:ds_get:)', ['names']) # ['test']
db.close()
```

## Integration Providers
Expand All @@ -105,13 +138,13 @@ pip install Faker
from rand import Rand


rand = Rand()
rand.gen('(:faker_hexify:)') # abc
rnd = Rand()
rnd.gen('(:faker_hexify:)') # abc
```

## Custom Providers

Below is sample code how to integrate existing class definition (TestProxy) to Rand.
Below is sample code on how to integrate an existing class definition (TestProxy) to Rand.

```python
from rand import Rand
Expand All @@ -124,20 +157,20 @@ class TestProxy:
return '%s-%s' % (arg1, arg2)

# init rand class
rand = Rand()
rnd = Rand()

# create proxy provider helper and register to rand
test_proxy = RandProxyBaseProvider(prefix='test', target=TestProxy())
rand.register_provider(test_proxy)
rnd.register_provider(test_proxy)

# test
print(rand.gen('(:test_target:)')) # ['def1-def2']
print(rand.gen('(:test_target:)', ['ok1'])) # ['ok1-def2']
print(rand.gen('(:test_target:)', ['ok1', 'ok2'])) # ['ok1-def2']
print(rand.gen('(:test_target:)', [['ok1', 'ok2']])) # ['ok1-ok2']
print(rand.gen('(:test_target:)', [['ok1', 'ok2'], 'ok3'])) # ['ok1-ok2']
print(rand.gen('(:test_target:)', [{'arg1': 'ok1'}])) # ['ok1-def2']
print(rand.gen('(:test_target:)', [{'arg1': 'ok1', 'arg2': 'ok2'}])) # ['ok1-ok2']
print(rnd.gen('(:test_target:)')) # ['def1-def2']
print(rnd.gen('(:test_target:)', ['ok1'])) # ['ok1-def2']
print(rnd.gen('(:test_target:)', ['ok1', 'ok2'])) # ['ok1-def2']
print(rnd.gen('(:test_target:)', [['ok1', 'ok2']])) # ['ok1-ok2']
print(rnd.gen('(:test_target:)', [['ok1', 'ok2'], 'ok3'])) # ['ok1-ok2']
print(rnd.gen('(:test_target:)', [{'arg1': 'ok1'}])) # ['ok1-def2']
print(rnd.gen('(:test_target:)', [{'arg1': 'ok1', 'arg2': 'ok2'}])) # ['ok1-ok2']
```

# Test
Expand All @@ -147,6 +180,7 @@ Run test by installing packages and run tox
```shell script
$ pip install poetry tox
$ tox
$ tox -e py36 -- tests/test_ds.py
```

For hot-reload development coding
Expand All @@ -157,11 +191,19 @@ $ nodemon -w rand --exec python -c "from rand import Rand"

# Help?

Any feedback,
Any feedback, suggestions and integration with 3rd-party libraries can be added using PR or create issues if needed helps.

# Similar Projects

List of projects similar to **rand**:
- [exrex](https://github.com/asciimoo/exrex): Irregular methods on regular expressions
- [xeger](https://github.com/crdoconnor/xeger): Library to generate random strings from regular expressions
- [strgen](https://github.com/paul-wolf/strgen): A Python module for a template language that generates randomized data

# Acknowdlge Projects

List of projects that **rand** depends on:
- [peewee](https://github.com/coleifer/peewee): a small, expressive orm -- supports postgresql, mysql and sqlite
- [pytest](https://github.com/pytest-dev/pytest/): The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
- [coverage](https://github.com/nedbat/coveragepy): Code coverage measurement for Python
- [pytest-cov](https://github.com/pytest-dev/pytest-cov): Coverage plugin for pytest
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"start": "nodemon -w rand --exec python -c \"from rand import Rand\"",
"test": "tox",
"deploy": "poetry export -f requirements.txt > requirements.txt && poetry update && poetry build && poetry deploy"
"export": "poetry export -f requirements.txt > requirements.txt",
"deploy": "npm run export && poetry update && poetry build && poetry deploy"
},
"repository": {
"type": "git",
Expand Down
13 changes: 12 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "rand"
version = "0.1.1"
version = "0.2.0"
description = "Generate String from regex pattern"
authors = ["kororo"]
license = "MIT"
Expand All @@ -13,6 +13,7 @@ packages = [
[tool.poetry.dependencies]
python = "^3.6"
pytest-cov = { version = "^2.0" }
peewee = "^3.13.3"

[tool.poetry.extras]
test = ["pytest"]
Expand Down
2 changes: 1 addition & 1 deletion rand/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import warnings
from rand.rand import Rand
from rand.rand import Rand, ParseFnType

warnings.simplefilter(action='ignore', category=FutureWarning)
60 changes: 46 additions & 14 deletions rand/providers/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import re
import typing

if typing.TYPE_CHECKING: # pragma: no cover
from rand import Rand
from rand import Rand, ParseFnType


class BaseRandAdapter:
Expand All @@ -20,18 +21,39 @@ def rand(self, rand):


class RandBaseProvider(BaseRandAdapter):
_prefix: str

def __init__(self, prefix: str = ''):
super(RandBaseProvider, self).__init__()
self.prefix = prefix

@property
def prefix(self) -> str:
return self._prefix

@prefix.setter
def prefix(self, prefix: str):
self._prefix = prefix

def get_parse_name(self, name: str) -> typing.Optional[str]:
# name always start with _parse_[PREFIX], normalise first
name = re.sub('^_parse_', '', name)
if name.startswith(self.prefix):
name = re.sub('^%s_' % self.prefix, '', name)
return name
return None

def parse(self, name: str, pattern: any, opts: dict): # pragma: no cover
return None

def register(self): # pragma: no cover
pass


class RandProxyBaseProvider(RandBaseProvider):
def __init__(self, prefix: str = '', target=None):
super(RandProxyBaseProvider, self).__init__(prefix=prefix)
self._target = target
self.target = target

@property
def target(self):
Expand All @@ -41,26 +63,36 @@ def target(self):
def target(self, target):
self._target = target

def proxy_parse(self):
def parse(ri, pattern, opts):
def proxy_parse(self, name: str):
def parse(pattern, opts):
token, args = opts['token'], opts['args']
name = token.replace('%s_' % self._prefix, '')
fn = getattr(self._target, name)
# name can be acquired from the pattern token, commented out for clarity
# name = token.replace('%s_' % self._prefix, '')
fn = getattr(self._target, name.replace('%s_' % self._prefix, ''))
if fn:
if isinstance(args, list):
return fn(*args)
elif isinstance(args, dict):
return fn(**args)
else:
return fn()
return ''

return parse

def parse(self, name: str, pattern: any, opts: dict):
# name always start with _parse_[PREFIX], normalise first
parsed_name = self.get_parse_name(name)
if parsed_name and callable(getattr(self._target, parsed_name, None)) and not parsed_name.startswith('_'):
return self.proxy_parse(parsed_name)(pattern, opts)
return None

def register(self):
names = [name for name in dir(self._target)
if callable(getattr(self._target, name))
if not name.startswith('_')
]
for name in names:
self.rand.register_parse('%s_%s' % (self._prefix, name), self.proxy_parse())
# registering parse can be explicit like this or by dynamic with get_parse (this is newer and flexible way)
# this code commented out for clarity
# names = [
# name for name in dir(self._target)
# if callable(getattr(self._target, name))
# if not name.startswith('_')
# ]
# for name in names:
# self.rand.register_parse('%s_%s' % (self._prefix, name), self.proxy_parse(name=name))
pass
4 changes: 0 additions & 4 deletions rand/providers/contribs/faker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ def __init__(self, prefix: str = 'faker', target=None):
from faker import Faker
target = target if target else Faker()
super(RandFakerProvider, self).__init__(prefix=prefix, target=target)

def register(self):
for name in ['hexify', 'numerify']:
self.rand.register_parse('%s_%s' % (self._prefix, name), self.proxy_parse())
1 change: 1 addition & 0 deletions rand/providers/ds/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .ds import *
Loading

0 comments on commit f9cf6c9

Please sign in to comment.