Skip to content

Commit

Permalink
Add url parser
Browse files Browse the repository at this point in the history
close #6
  • Loading branch information
sloria committed Jan 9, 2017
1 parent 8d33515 commit 38d5f84
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 3 deletions.
16 changes: 16 additions & 0 deletions environs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import json as pyjson
import os
import re
try:
import urllib.parse as urlparse
except ImportError:
# Python 2
import urlparse

import marshmallow as ma
from read_env import read_env as _read_env
Expand Down Expand Up @@ -94,6 +99,16 @@ def _preprocess_dict(value, **kwargs):
def _preprocess_json(value, **kwargs):
return pyjson.loads(value)

class URLField(ma.fields.URL):
def _serialize(self, value, attr, obj):
return value.geturl()

# Override deserialize rather than _deserialize because we need
# to call urlparse *after* validation has occurred
def deserialize(self, value, attr=None, data=None):
ret = super(URLField, self).deserialize(value, attr, data)
return urlparse.urlparse(ret)

class Env(object):
"""An environment variable reader."""
__call__ = _field2method(ma.fields.Field, '__call__')
Expand All @@ -115,6 +130,7 @@ def __init__(self):
date=_field2method(ma.fields.Date, 'date'),
timedelta=_field2method(ma.fields.TimeDelta, 'timedelta'),
uuid=_field2method(ma.fields.UUID, 'uuid'),
url=_field2method(URLField, 'url'),
)

def __repr__(self):
Expand Down
36 changes: 33 additions & 3 deletions tests/test_environs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import uuid
from decimal import Decimal
import datetime as dt
try:
import urllib.parse as urlparse
except ImportError:
# Python 2
import urlparse

import pytest
from marshmallow import fields, validate
Expand All @@ -17,7 +22,7 @@ def _set_env(envvars):
return _set_env


@pytest.fixture
@pytest.fixture(scope='function')
def env():
return environs.Env()

Expand All @@ -44,7 +49,7 @@ def test_int_cast(self, set_env, env):
def test_invalid_int(self, set_env, env):
set_env({'INT': 'invalid'})
with pytest.raises(environs.EnvError) as excinfo:
env.int('INT') == 42
env.int('INT')
assert 'Environment variable "INT" invalid' in excinfo.value.args[0]

def test_float_cast(self, set_env, env):
Expand Down Expand Up @@ -122,6 +127,23 @@ def test_uuid_cast(self, set_env, env):
set_env({'UUID': str(uid)})
assert env.uuid('UUID') == uid

def test_url_cast(self, set_env, env):
set_env({'URL': 'http://stevenloria.com/projects/?foo=42'})
res = env.url('URL')
assert isinstance(res, urlparse.ParseResult)

@pytest.mark.parametrize('url',
[
'foo',
'42',
'foo@bar',
])
def test_invalid_url(self, url, set_env, env):
set_env({'URL': url})
with pytest.raises(environs.EnvError) as excinfo:
env.url('URL')
assert 'Environment variable "URL" invalid' in excinfo.value.args[0]


class TestProxiedVariables:

Expand Down Expand Up @@ -248,17 +270,25 @@ def _deserialize(self, value, *args, **kwargs):
class TestDumping:
def test_dump(self, set_env, env):
dtime = dt.datetime.utcnow()
set_env({'STR': 'foo', 'INT': '42', 'DTIME': dtime.isoformat()})
set_env({
'STR': 'foo',
'INT': '42',
'DTIME': dtime.isoformat(),
'URLPARSE': 'http://stevenloria.com/projects/?foo=42',
})

env.str('STR')
env.int('INT')
env.datetime('DTIME')
env.url('URLPARSE')

result = env.dump()
assert result['STR'] == 'foo'
assert result['INT'] == 42
assert 'DTIME' in result
assert type(result['DTIME']) is str
assert type(result['URLPARSE']) is str
assert result['URLPARSE'] == 'http://stevenloria.com/projects/?foo=42'

def test_env_with_custom_parser(self, set_env, env):
@env.parser_for('url')
Expand Down

0 comments on commit 38d5f84

Please sign in to comment.