Skip to content

Commit cd01290

Browse files
authored
Merge pull request #65 from geoadmin/develop
New Release v4.0.0 - #major
2 parents cebf2af + 19c1eb0 commit cd01290

File tree

11 files changed

+800
-634
lines changed

11 files changed

+800
-634
lines changed

.pylintrc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,6 @@ min-public-methods=2
522522

523523
# Exceptions that will emit a warning when being caught. Defaults to
524524
# "BaseException, Exception".
525-
overgeneral-exceptions=BaseException,
526-
Exception,
527-
StandardError
525+
overgeneral-exceptions=builtins.BaseException,
526+
builtins.Exception,
527+
builtins.StandardError

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.9-slim-buster
1+
FROM python:3.11-slim-buster
22
RUN groupadd -r geoadmin && useradd -r -s /bin/false -g geoadmin geoadmin
33

44

Pipfile

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ verify_ssl = true
44
name = "pypi"
55

66
[packages]
7-
boto3 = "~=1.23.0"
8-
logging-utilities = "~=3.0"
9-
Flask = "~=2.1.0"
10-
gevent = "~=21.12.0"
11-
gunicorn = "~=20.1.0"
12-
PyYAML = ">=5.4"
13-
python-dotenv = "~=0.20.0"
14-
validators = "~=0.19.0"
7+
boto3 = "~=1.28"
8+
logging-utilities = "~=4.0"
9+
Flask = "~=3.0.0"
10+
gevent = "~=23.9"
11+
gunicorn = "~=21.2"
12+
PyYAML = "~=6.0"
13+
python-dotenv = "~=1.0"
14+
validators = "==0.20" # breaking change in 0.21 and 0.22 (# in url path). To be fixed in >=0.23
15+
nanoid = "~=2.0"
1516

1617
[dev-packages]
17-
yapf = "~=0.30.0"
18-
moto = "~=3.1.9"
18+
yapf = "~=0.40"
19+
moto = "~=4.2"
1920
nose2 = "*"
2021
pylint = "*"
2122
pylint-flask = "*"
2223

2324
[requires]
24-
python_version = "3.9"
25+
python_version = "3.11"

Pipfile.lock

Lines changed: 732 additions & 592 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ Each image contains the following metadata:
153153
These metadata can be read with the following command
154154

155155
```bash
156-
# NOTE: Currently we don't have permission to do docker pull on AWS ECR
157156
make dockerlogin
158157
docker pull 974517877189.dkr.ecr.eu-central-1.amazonaws.com/service-shortcut:develop.latest
159158

@@ -195,15 +194,17 @@ The service is configured by Environment Variable:
195194

196195
| Env Variable | Default | Description |
197196
| --------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198-
| LOGGING_CFG | logging-cfg-local.yml | Logging configuration file to use. |
199-
| AWS_ACCESS_KEY_ID | None | Necessary credential to access dynamodb |
200-
| AWS_SECRET_ACCESS_KEY | None | AWS_SECRET_ACCESS_KEY | |
201-
| AWS_DYNAMODB_TABLE_NAME | | The dynamodb table name |
202-
| AWS_DEFAULT_REGION | eu-central-1 | The AWS region in which the table is hosted. |
203-
| AWS_ENDPOINT_URL | | The AWS endpoint url to use |
204-
| ALLOWED_DOMAINS | `.*` | A comma separated list of allowed domains names |
205-
| FORWARED_ALLOW_IPS | `*` | Sets the gunicorn `forwarded_allow_ips` (see https://docs.gunicorn.org/en/stable/settings.html#forwarded-allow-ips). This is required in order to `secure_scheme_headers` works. |
206-
| FORWARDED_PROTO_HEADER_NAME | `X-Forwarded-Proto` | Sets gunicorn `secure_scheme_headers` parameter to `{FORWARDED_PROTO_HEADER_NAME: 'https'}`, see https://docs.gunicorn.org/en/stable/settings.html#secure-scheme-headers. |
207-
| CACHE_CONTROL | `public, max-age=31536000` | Cache Control header value of the `GET /<shortlink>` endpoint |
208-
| CACHE_CONTROL_4XX | `public, max-age=3600` | Cache Control header for 4XX responses |
209-
| GUNICORN_WORKER_TMP_DIR | `None` | This should be set to an tmpfs file system for better performance. See https://docs.gunicorn.org/en/stable/settings.html#worker-tmp-dir. |
197+
| LOGGING_CFG | `logging-cfg-local.yml` | Logging configuration file to use. |
198+
| AWS_ACCESS_KEY_ID | | Necessary credential to access dynamodb |
199+
| AWS_SECRET_ACCESS_KEY | | AWS_SECRET_ACCESS_KEY | |
200+
| AWS_DYNAMODB_TABLE_NAME | | The dynamodb table name |
201+
| AWS_DEFAULT_REGION | eu-central-1 | The AWS region in which the table is hosted. |
202+
| AWS_ENDPOINT_URL | | The AWS endpoint url to use |
203+
| ALLOWED_DOMAINS | `.*` | A comma separated list of allowed domains names |
204+
| FORWARED_ALLOW_IPS | `*` | Sets the gunicorn `forwarded_allow_ips` (see https://docs.gunicorn.org/en/stable/settings.html#forwarded-allow-ips). This is required in order to `secure_scheme_headers` works. |
205+
| FORWARDED_PROTO_HEADER_NAME | `X-Forwarded-Proto` | Sets gunicorn `secure_scheme_headers` parameter to `{FORWARDED_PROTO_HEADER_NAME: 'https'}`, see https://docs.gunicorn.org/en/stable/settings.html#secure-scheme-headers. |
206+
| CACHE_CONTROL | `public, max-age=31536000` | Cache Control header value of the `GET /<shortlink>` endpoint |
207+
| CACHE_CONTROL_4XX | `public, max-age=3600` | Cache Control header for 4XX responses |
208+
| GUNICORN_WORKER_TMP_DIR | | This should be set to an tmpfs file system for better performance. See https://docs.gunicorn.org/en/stable/settings.html#worker-tmp-dir. |
209+
| SHORT_ID_SIZE | `12` | The size (number of characters) of the shortloink id's
210+
| SHORT_ID_ALPHABET | `0123456789abcdefghijklmnopqrstuvwxyz` | The alphabet (characters) used by the shortlink. Allowed chars `[0-9][A-Z][a-z]-_`

app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,4 @@ def handle_exception(err):
152152
return make_error_msg(500, "Internal server error, please consult logs")
153153

154154

155-
from app import routes # isort:skip pylint: disable=ungrouped-imports, wrong-import-position
155+
from app import routes # isort:skip pylint: disable=ungrouped-imports, wrong-import-position, cyclic-import

app/helpers/utils.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22
import logging.config
33
import os
44
import re
5-
import time
6-
from distutils.util import strtobool
75
from itertools import chain
86
from pathlib import Path
97

108
import validators
119
import yaml
10+
from nanoid import generate
1211

1312
from flask import abort
1413
from flask import jsonify
1514
from flask import make_response
1615
from flask import request
1716

1817
from app.settings import ALLOWED_DOMAINS_PATTERN
18+
from app.settings import SHORT_ID_ALPHABET
19+
from app.settings import SHORT_ID_SIZE
1920

2021
logger = logging.getLogger(__name__)
2122

@@ -70,8 +71,7 @@ def get_redirect_param(ignore_errors=False):
7071

7172

7273
def generate_short_id():
73-
# datetime.datetime(2001, 9, 9, 3, 46, 40) * 1000 = 1000000000000
74-
return f'{int(time.time() * 1000) - 1000000000000:x}'
74+
return generate(SHORT_ID_ALPHABET, SHORT_ID_SIZE)
7575

7676

7777
def make_error_msg(code, msg):
@@ -117,3 +117,17 @@ def get_url():
117117
abort(400, 'URL given as a parameter is not allowed.')
118118

119119
return url
120+
121+
122+
def strtobool(value) -> bool:
123+
"""Convert a string representation of truth to true (1) or false (0).
124+
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
125+
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
126+
'val' is anything else.
127+
"""
128+
value = value.lower()
129+
if value in ('y', 'yes', 't', 'true', 'on', '1'):
130+
return True
131+
if value in ('n', 'no', 'f', 'false', 'off', '0'):
132+
return False
133+
raise ValueError(f"invalid truth value \'{value}\'")

app/routes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ def create_shortlink():
5151
@app.route('/<shortlink_id>', methods=['GET'])
5252
def get_shortlink(shortlink_id):
5353
"""
54-
This route checks the shortened url id and redirect the user to the full url.
54+
This route checks the shortened url id and redirect the user to the full url.
5555
When the redirect query parameter is set to false, it will return a json containing
5656
the information about the shortlink.
5757
"""
58-
should_redirect = get_redirect_param()
58+
5959
db_entry = get_db().get_entry_by_shortlink(shortlink_id)
6060
if db_entry is None:
6161
abort(404, f'No short url found for {shortlink_id}')
6262

63-
if should_redirect:
63+
if get_redirect_param():
6464
logger.debug("redirecting to the following url : %s", db_entry['url'])
6565
return redirect(db_entry['url'], code=301)
6666

app/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
STAGING = os.environ['STAGING']
2727

2828
COLLISION_MAX_RETRY = 10
29-
SHORT_ID_SIZE = 10
29+
30+
SHORT_ID_SIZE = int(os.getenv('SHORT_ID_SIZE', '12'))
31+
SHORT_ID_ALPHABET = os.getenv('SHORT_ID_ALPHABET', '0123456789abcdefghijklmnopqrstuvwxyz')
3032

3133
GUNICORN_WORKER_TMP_DIR = os.getenv("GUNICORN_WORKER_TMP_DIR", None)

tests/unit_tests/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# The mocking is placed here to make sure it is started earliest possible.
22
# see: https://github.com/spulec/moto#very-important----recommended-usage
3-
from moto import mock_dynamodb2
3+
from moto import mock_dynamodb
44

5-
dynamodb = mock_dynamodb2()
5+
dynamodb = mock_dynamodb()
66
dynamodb.start()

0 commit comments

Comments
 (0)