Add `CommaSeparatedStrings` datatype (#274)

* Add CommaSeparatedStrings datatype

* Version 0.9.9
This commit is contained in:
Tom Christie 2018-12-14 16:22:31 +00:00 committed by GitHub
parent 96c09044e8
commit fe2b926009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 4 deletions

View File

@ -9,7 +9,7 @@ that is not committed to source control.
```python ```python
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.config import Config from starlette.config import Config
from starlette.datastructures import DatabaseURL, Secret from starlette.datastructures import CommaSeparatedStrings, DatabaseURL, Secret
# Config will be read from environment variables and/or ".env" files. # Config will be read from environment variables and/or ".env" files.
config = Config(".env") config = Config(".env")
@ -17,6 +17,7 @@ config = Config(".env")
DEBUG = config('DEBUG', cast=bool, default=False) DEBUG = config('DEBUG', cast=bool, default=False)
DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL) DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL)
SECRET_KEY = config('SECRET_KEY', cast=Secret) SECRET_KEY = config('SECRET_KEY', cast=Secret)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)
app = Starlette() app = Starlette()
app.debug = DEBUG app.debug = DEBUG
@ -31,6 +32,7 @@ app.debug = DEBUG
DEBUG=True DEBUG=True
DATABASE_URL=postgresql://localhost/myproject DATABASE_URL=postgresql://localhost/myproject
SECRET_KEY=43n080musdfjt54t-09sdgr SECRET_KEY=43n080musdfjt54t-09sdgr
ALLOWED_HOSTS="127.0.0.1", "localhost"
``` ```
## Configuration precedence ## Configuration precedence
@ -45,8 +47,8 @@ If none of those match, then `config(...)` will raise an error.
## Secrets ## Secrets
For sensitive keys, the `Secret` class is useful, since it prevents the value from For sensitive keys, the `Secret` class is useful, since it helps minimize
leaking out into tracebacks or logging. occasions where the value it holds could leak out into tracebacks or logging.
To get the value of a `Secret` instance, you must explicitly cast it to a string. To get the value of a `Secret` instance, you must explicitly cast it to a string.
You should only do this at the point at which the value is used. You should only do this at the point at which the value is used.

View File

@ -1 +1 @@
__version__ = "0.9.8" __version__ = "0.9.9"

View File

@ -1,5 +1,7 @@
import typing import typing
from collections import namedtuple from collections import namedtuple
from collections.abc import Sequence
from shlex import shlex
from urllib.parse import ParseResult, parse_qsl, urlencode, urlparse from urllib.parse import ParseResult, parse_qsl, urlencode, urlparse
from starlette.types import Scope from starlette.types import Scope
@ -195,6 +197,33 @@ class Secret:
return self._value return self._value
class CommaSeparatedStrings(Sequence):
def __init__(self, value: typing.Union[str, typing.Sequence[str]]):
if isinstance(value, str):
splitter = shlex(value, posix=True)
splitter.whitespace = ","
splitter.whitespace_split = True
self._items = [item.strip() for item in splitter]
else:
self._items = list(value)
def __len__(self) -> int:
return len(self._items)
def __getitem__(self, index: typing.Union[int, slice]) -> typing.Any:
return self._items[index]
def __iter__(self) -> typing.Iterator[str]:
return iter(self._items)
def __repr__(self) -> str:
list_repr = repr([item for item in self])
return "%s(%s)" % (self.__class__.__name__, list_repr)
def __str__(self) -> str:
return ", ".join([repr(item) for item in self])
class QueryParams(typing.Mapping[str, str]): class QueryParams(typing.Mapping[str, str]):
""" """
An immutable multidict. An immutable multidict.

View File

@ -1,5 +1,6 @@
from starlette.datastructures import ( from starlette.datastructures import (
URL, URL,
CommaSeparatedStrings,
DatabaseURL, DatabaseURL,
Headers, Headers,
MutableHeaders, MutableHeaders,
@ -59,6 +60,30 @@ def test_database_url():
assert u.driver == "asyncpg" assert u.driver == "asyncpg"
def test_csv():
csv = CommaSeparatedStrings('"localhost", "127.0.0.1", 0.0.0.0')
assert list(csv) == ["localhost", "127.0.0.1", "0.0.0.0"]
assert repr(csv) == "CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])"
assert str(csv) == "'localhost', '127.0.0.1', '0.0.0.0'"
assert csv[0] == "localhost"
assert len(csv) == 3
csv = CommaSeparatedStrings("'localhost', '127.0.0.1', 0.0.0.0")
assert list(csv) == ["localhost", "127.0.0.1", "0.0.0.0"]
assert repr(csv) == "CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])"
assert str(csv) == "'localhost', '127.0.0.1', '0.0.0.0'"
csv = CommaSeparatedStrings("localhost, 127.0.0.1, 0.0.0.0")
assert list(csv) == ["localhost", "127.0.0.1", "0.0.0.0"]
assert repr(csv) == "CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])"
assert str(csv) == "'localhost', '127.0.0.1', '0.0.0.0'"
csv = CommaSeparatedStrings(["localhost", "127.0.0.1", "0.0.0.0"])
assert list(csv) == ["localhost", "127.0.0.1", "0.0.0.0"]
assert repr(csv) == "CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])"
assert str(csv) == "'localhost', '127.0.0.1', '0.0.0.0'"
def test_url_from_scope(): def test_url_from_scope():
u = URL( u = URL(
scope={"path": "/path/to/somewhere", "query_string": b"abc=123", "headers": []} scope={"path": "/path/to/somewhere", "query_string": b"abc=123", "headers": []}