Skip to content

Commit

Permalink
Merge 1769fb0 into 54ff3ed
Browse files Browse the repository at this point in the history
  • Loading branch information
amitt001 committed Jul 12, 2018
2 parents 54ff3ed + 1769fb0 commit 4d4fd68
Show file tree
Hide file tree
Showing 111 changed files with 699 additions and 121 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Files
*.pyc
bootstrap.min.css
*.db
*.log
.coverage
bootstrap.min.css

# Directory
*.egg-info/
env
env3
.idea
.vscode
dist
.DS_Store
.cache
.pytest_cache/
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ install:
- pip install coveralls

script:
- coverage run --source src/pygmy -m py.test
- coverage run --source pygmy -m py.test

after_success:
- coveralls
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p align="center"><img src="src/pyui/static/logo/logov2.png" alt="pygmy" height="200px"></p>
<p align="center"><img src="pygmyui/static/logo/logov2.png" alt="pygmy" height="200px"></p>

Pygmy
=====
Expand Down Expand Up @@ -55,7 +55,7 @@ Technical Info

- Python 3, Javascript, JQuery, HTML, CSS
- REST API: Flask
- Pyui: Django(It serves the web user interface)
- Pygmyui: Django(It serves the web user interface)
- DB: PostgreSQL/MySQL/SQLite
- Others: SQLAlchmey, JWT

Expand All @@ -77,12 +77,12 @@ NOTE: **This module only supports Python 3. Make sure pip and virtualenv are bot
Note:

1. The project has two config files:
- pygmy.cfg: `src/pygmy/config/pygmy.cfg` rest API and pygmy core settings file
- settings.py: `src/pyui/pyui/settings.py` Django settings file
- pygmy.cfg: `pygmy/config/pygmy.cfg` rest API and pygmy core settings file
- settings.py: `pygmyui/pygmyui/settings.py` Django settings file
2. SQLite is default db, if you are using PostgreSQL or MySQL with this project, make sure they are installed into the system.
3. To modify config settings vim `src/pygmy/config/pygmy.cfg`
3. To modify config settings vim `pygmy/config/pygmy.cfg`
4. You can run pygmy shell present in src directory to run the program on terminal. `python shell`
5. By default in `src/pyui/pyui/settings.py` DEBUG is set to True, set it to False in production
5. By default in `pygmyui/pygmyui/settings.py` DEBUG is set to True, set it to False in production

DB Setup:
=========
Expand All @@ -98,7 +98,7 @@ Check correct port:

`mysqladmin variables | grep port`

Change below line in `src/pygmy/core/pygmy.cfg`:
Change below line in `pygmy/core/pygmy.cfg`:

```
engine: mysql
Expand Down Expand Up @@ -150,7 +150,7 @@ Get Link:
Shell Usage
===========

Open shell using ./pygmy/src/shell. Available context is pygmy, Config, DB, etc. See all context by using pygmy_context.
Open shell using ./shell. Available context is pygmy, Config, DB, etc. See all context by using pygmy_context.

Shorten a link:

Expand Down Expand Up @@ -225,7 +225,7 @@ Docstring:
How Link Stats Are Generated?
=============================

For getting geo location stats from IP maxminds' [GeoLite2-Country.mmd](http://pygy.co/cm) database is used. It's in `src/pygmy/app` directory.
For getting geo location stats from IP maxminds' [GeoLite2-Country.mmd](http://pygy.co/cm) database is used. It's in `pygmy/app` directory.

How Pygmy Auth Token Works?
===========================
Expand All @@ -237,7 +237,7 @@ Development

Run tests and generate a coverage report:

`coverage run --source src/pygmy -m py.test`
`coverage run --source pygmy -m py.test`

See coverage report:

Expand All @@ -258,6 +258,26 @@ I would like to thank DigitalOcean for providing initial hosting to Pygmy projec
License
=======

The MIT license (MIT)
MIT License

Copyright (c) 2017 Amit Tripathi(https://twitter.com/amitt019)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

[Read License Terms](https://github.com/amitt001/pygmy/blob/master/LICENSE)
8 changes: 4 additions & 4 deletions init.d/pyui
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/bin/bash
# init.d script for pyui app
# init.d script for pygmyui app

NAME=pyui
NAME=pygmyui
PIDFILE=/var/run/$NAME.pid

PORT=8000
APP_DIR=/opt/pygyco/src/pyui
APP_DIR=/opt/pygyco/pygmyui
PYTHONPATH='/opt/pygyco/env/bin/gunicorn'
APP_ARGS='--log-file /var/log/pygmy/uierror_logs.log --access-logfile /var/log/pygmy/uiacclogs.log --bind 127.0.0.1:8000 --workers 2 pyui.wsgi'
APP_ARGS='--log-file /var/log/pygmy/uierror_logs.log --access-logfile /var/log/pygmy/uiacclogs.log --bind 127.0.0.1:8000 --workers 2 pygmyui.wsgi'
APP_STOP_ARGS='--stop $PIDFILE'
APP_RELOAD_ARGS='--reload $PIDFILE'

Expand Down
1 change: 1 addition & 0 deletions pygmy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name = 'pygmy'
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions src/pygmy/app/link.py → pygmy/app/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ def formatted_link_stats(link):
'time_series_base': click_meta.get('time_base'),
'time_stats': click_meta.get('timestamp_hits', {}),
}

# Hide original/long_url in case of protected links
if link.is_protected is True:
link_info['long_url'] = ''
return {**link_info, **click_info}


Expand Down
File renamed without changes.
13 changes: 11 additions & 2 deletions src/pygmy/config/config.py → pygmy/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ class Configuration:
def __init__(self):
# default sqlite3
# TODO: Take this from cfg files
self.default_config_path = 'pygmy/config/pygmy.cfg'
self.cfg = None
self.debug = False
self.schema = None
self.host = None
self.port = None
self.secret = None
self.logging = None
self.webservice_url = None
self.database = None
self.initialize()

def _read_cfg(self):
self.cfg = ConfigParser()
self.cfg.read(os.environ[_CONFIG_ENV_VAR])
self.cfg.read(os.environ.get(_CONFIG_ENV_VAR, self.default_config_path))

def __getattr__(self, name):
"""Add sections dynamically. With this no need to define
Expand All @@ -45,7 +48,13 @@ def initialize(self):
self.schema = self.cfg['pygmy']['schema']
self.host = self.cfg['pygmy']['host']
self.port = self.cfg['pygmy']['port']
self.secret = self.cfg['rest']['flask_secret']
self.secret = self.cfg['pygmy']['flask_secret']
self.logging = self.cfg['logging']
self.webservice_url = "{0}://{1}:{2}".format(
self.schema, self.host, self.port)

if self.database['engine'] == 'sqlite3':
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sqlite_path = root_dir + '/' + self.database['data_dir'] + '/' + self.database['file_name']
self.database['url'] = 'sqlite:///{}'.format(sqlite_path)

15 changes: 7 additions & 8 deletions src/pygmy/config/pygmy.cfg → pygmy/config/pygmy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,14 @@ short_url_schema = http://
[database]
file_name: pygmy.db
engine: sqlite3
url: sqlite:////var/lib/pygmy/pygmy.db
# postgres://amit@127.0.0.1:5432/pygmy
# sqlite:////var/lib/pygmy/pygmy.db
data_dir = data
# url: postgres://amit@127.0.0.1:5432/pygmy
# mysql+pymysql://root:root@127.0.0.1:3306/pygmy
user:
password:
host:
port:

[rest]
host = 127.0.0.1
port = 9119
flask_secret = CvJHGFVBj*&^TRGBHDdBV836bdy73JJDHGV

[auth]
refresh_secret = _))_((*REFRESH)(*_)+_
access_secret = +_)R@nd0m(_+
Expand All @@ -38,3 +32,8 @@ jwt_token_blacklist_engine = None

[pygmy_internal]
pygmy_header_key = KJ*57*6)(*&^dh

[logging]
filepath = pygmy/data/pygmy.log
level = DEBUG
format = %%(asctime)s - %%(name)s - %%(levelname)s - %%(message)s
17 changes: 9 additions & 8 deletions src/pygmy/tests/pygmy_test.cfg → pygmy/config/pygmy_test.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,23 @@
schema = http
host = 0.0.0.0
port = 9118
debug = False
debug = True
flask_secret = CvJHGFVBj*&^TRGBHDdBV836bdy73JJDHGV
short_url = 127.0.0.1
short_url_schema = http://

[database]
file_name: pygmy_test.db
engine: sqlite3
url: sqlite:////var/lib/pygmy/pygmy_test.db
# postgres://amit@127.0.0.1:5432/pygmy
data_dir = data
# url: postgres://amit@127.0.0.1:5432/pygmy
# sqlite:////var/lib/pygmy/pygmy.db
# mysql+pymysql://root:root@127.0.0.1:3306/pygmy
user:
password:
host:
port:

[rest]
host = 127.0.0.1
port = 9118
flask_secret = CvJHGFVBj*&^TRGBHDdBV836bdy73JJDHGV

[auth]
refresh_secret = _))_((*REFRESH)(*_)+_
access_secret = +_)R@nd0m(_+
Expand All @@ -38,3 +33,9 @@ jwt_token_blacklist_engine = None

[pygmy_internal]
pygmy_header_key = KJ*57*6)(*&^dh


[logging]
filepath = pygmy/data/pygmy_test.log
level = DEBUG
format = %%(asctime)s - %%(name)s - %%(levelname)s - %%(message)s
File renamed without changes.
1 change: 0 additions & 1 deletion src/pygmy/core/hashdigest.py → pygmy/core/hashdigest.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,3 @@ def unshorten(self, s):
for i in range(1000):
sh = hd.shorten(1099)
hd.decode(sh)
print(time.time() - t)
3 changes: 2 additions & 1 deletion src/pygmy/core/initialize.py → pygmy/core/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
_CONFIG_ENV_VAR = 'PYGMY_CONFIG_FILE'
_CFG_PATHS = ['pygmy/config/pygmy.cfg', 'pygmy.cfg',
'$HOME/.pygmy.cfg', '/etc/pygmy.cfg'
'tests/pygmy_test.cfg']
'tests/pygmy_test.cfg', 'pygmy/config/pygmy_test.cfg']


def load_config_path(config_path=None):
Expand All @@ -29,6 +29,7 @@ def initialize_config(config_path=None):
load_config_path(config_path)
# If config not found, exit the program. Else call config initialize.
if os.environ.get(_CONFIG_ENV_VAR) is None:
print('\nNo _CONFIG_ENV_VAR variable set. Exiting...')
sys.exit(1)
config.initialize()

Expand Down
41 changes: 41 additions & 0 deletions pygmy/core/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import logging
import threading

from pygmy.config import config


class Logger:
"""Configure singleton logger for application and return its instance."""
_lock = threading.Lock()
_instance = None

def __new__(cls, filename=None, level=logging.DEBUG, format=None):
log_config = {'level': getattr(logging, level, logging.DEBUG)}
if filename:
log_config.update({'filename': filename})
if format:
log_config.update({'format': format})

with cls._lock:
if cls._instance is None:
logging.basicConfig(**log_config)
logger = logging.getLogger(__name__)
cls._instance = logger
cls._instance.info('Logger created for {}'.format(__name__))
cls._add_stream_handler(level, format)

cls._instance.info('Logging setup done for {}'.format(__name__))
return cls._instance

@classmethod
def _add_stream_handler(cls, level, format):
cls._instance.info('Adding stream handler to logger')
handler = logging.StreamHandler()
handler.setLevel(level)
if format:
formatter = logging.Formatter(format)
handler.setFormatter(formatter)
cls._instance.addHandler(handler)


log = Logger(config.logging['filepath'], config.logging['level'], config.logging['format'])
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions src/pygmy/database/base.py → pygmy/database/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from sqlalchemy.ext.declarative import declarative_base

from pygmy.config import config
from pygmy.core.logger import log


class BaseDatabase:
Expand All @@ -25,6 +26,7 @@ def abort(self):

def initialize(self, debug=False):
self.url = config.database['url']
log.info('DB URL: {}'.format(self.url))
self._prepare(self.url)
self.engine = create_engine(self.url, echo=debug)
session = sessionmaker(bind=self.engine)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions src/pygmy/model/link.py → pygmy/model/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def add(self, db, long_url, **kwargs):
try:
db.commit()
except IntegrityError:
db.rollback()
raise ShortURLUnavailable('Short URL already taken or unavailable')
return self.link

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
9 changes: 7 additions & 2 deletions src/pygmy/rest/shorturl.py → pygmy/rest/shorturl.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pygmy.model import LinkManager, UserManager
from pygmy.validator import LinkSchema
from pygmy.utilities.urls import validate_url
from pygmy.core.logger import log


class LongUrlApi(MethodView):
Expand Down Expand Up @@ -41,8 +42,13 @@ def post(self):
return jsonify(dict(error='Invalid user')), 400
data['owner'] = user.id
if errors:
log.error('Error in the request payload %s', errors)
if errors.get('long_url'):
errors.update({'error': errors.get('long_url')})
return jsonify(errors), 400

long_url = data.pop('long_url')
log.info('Shortening url %s', long_url)
link = self.manager.get(long_url)
if link is None or (
data.get('is_custom') or
Expand All @@ -53,6 +59,7 @@ def post(self):
except ShortURLUnavailable as e:
return jsonify(dict(error=str(e))), 400
result = self.schema.dump(link)
log.info('Url: %s shortened, response: %s', long_url, result.data.get('short_code'))
return jsonify(result.data), 201


Expand Down Expand Up @@ -91,8 +98,6 @@ def resolve(code):
secret_key = request.headers.get('secret_key')
try:
# check if link is not a secret link
long_url = resolve_short(
code.strip('+'), secret_key=secret_key)
if code.startswith('+') or code.endswith('+'):
stats = link_stats(code)
response = jsonify(stats)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 4d4fd68

Please sign in to comment.