Skip to content

Commit

Permalink
* **Update:** Stalker is now compatible with SQLAlchemy 1.4, psycopg2…
Browse files Browse the repository at this point in the history
…-binary 2.86 and Python 3.9+. But more work still needs to be done to make it SQLAlchemy 2.0 compatible.
  • Loading branch information
eoyilmaz committed Jun 6, 2021
1 parent 3d2eecb commit 3e9a8db
Show file tree
Hide file tree
Showing 19 changed files with 155 additions and 127 deletions.
9 changes: 5 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@

python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9

services:
- postgresql

addons:
postgresql: "9.6"
postgresql: "13.6"

install:
- gem install taskjuggler
- pip install sqlalchemy "psycopg2-binary<=2.8.3" jinja2 alembic Mako MarkupSafe python-editor pytz tzlocal pytest pytest-xdist pytest-cov codeclimate-test-reporter
- pip install sqlalchemy psycopg2-binary jinja2 alembic Mako MarkupSafe python-editor pytz tzlocal pytest pytest-xdist pytest-cov codeclimate-test-reporter
- pip install pytest --upgrade

before_script:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
Stalker Changes
===============

0.2.25
======

* **Update:** Stalker is now compatible with SQLAlchemy 1.4,
psycopg2-binary 2.86 and Python 3.9+. But more work still needs to be done to
make it SQLAlchemy 2.0 compatible.

0.2.24.3
========

Expand Down
15 changes: 6 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
'houdini', 'nuke', 'fusion', 'softimage', 'blender', 'vue']
CLASSIFIERS = ["Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: OS Independent",
"Development Status :: 5 - Production/Stable",
Expand All @@ -23,7 +23,7 @@
"Topic :: Utilities",
"Topic :: Office/Business :: Scheduling", ]
INSTALL_REQUIRES = [
'psycopg2-binary<=2.8.3', 'sqlalchemy', 'alembic', 'jinja2', 'pytz', 'tzlocal',
'psycopg2-binary', 'sqlalchemy', 'alembic', 'jinja2', 'pytz', 'tzlocal',
]
TESTS_REQUIRE = ['pytest', 'pytest-xdist', 'pytest-cov', 'coverage']
DATA_FILES = [(
Expand All @@ -35,8 +35,7 @@


def read(*parts):
"""
Build an absolute path from *parts* and and return the contents of the
"""Build an absolute path from *parts* and and return the contents of the
resulting file. Assume UTF-8 encoding.
"""
with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f:
Expand All @@ -49,8 +48,7 @@ def read(*parts):


def find_meta(meta):
"""
Extract __*meta*__ from META_FILE.
"""Extract __*meta*__ from META_FILE.
"""
import re
meta_match = re.search(
Expand Down Expand Up @@ -82,4 +80,3 @@ def find_meta(meta):
install_requires=INSTALL_REQUIRES,
tests_require=TESTS_REQUIRE
)

13 changes: 5 additions & 8 deletions stalker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import sys

__version__ = '0.2.24.3'
__version__ = '0.2.25'

__title__ = "stalker"
__description__ = 'A Production Asset Management (ProdAM) System'
__uri__ = 'http://github.com/eoyilmaz/stalker'
__doc__ = __description__ + " <" + __uri__ + ">"
__doc__ = "%s <%s>" % (__description__, __uri__)

__author__ = "Erkan Ozgur Yilmaz"
__email__ = '[email protected]'
Expand All @@ -33,11 +33,9 @@
from stalker import config
defaults = config.Config()

from stalker.models.auth import (Group, Permission, User, LocalSession, Role,
AuthenticationLog)
from stalker.models.auth import Group, Permission, User, LocalSession, Role, AuthenticationLog
from stalker.models.asset import Asset
from stalker.models.budget import (Budget, BudgetEntry, Good, PriceList,
Invoice, Payment)
from stalker.models.budget import Budget, BudgetEntry, Good, PriceList, Invoice, Payment
from stalker.models.client import Client, ClientUser
from stalker.models.department import Department, DepartmentUser
from stalker.models.entity import SimpleEntity, Entity, EntityGroup
Expand All @@ -50,8 +48,7 @@
TargetEntityTypeMixin, UnitMixin,
WorkingHoursMixin)
from stalker.models.note import Note
from stalker.models.project import (Project, ProjectUser, ProjectClient,
ProjectRepository)
from stalker.models.project import Project, ProjectUser, ProjectClient, ProjectRepository
from stalker.models.review import Review, Daily, DailyLink
from stalker.models.repository import Repository
from stalker.models.scene import Scene
Expand Down
1 change: 1 addition & 0 deletions stalker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class Config(ConfigBase):
database_engine_settings={
"sqlalchemy.url": "sqlite://",
"sqlalchemy.echo": False,
# "sqlalchemy.pool_pre_ping": True,
},

database_session_settings={},
Expand Down
37 changes: 20 additions & 17 deletions stalker/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ def setup(settings=None):
# create the Session class
from stalker.db.session import DBSession
DBSession.remove()
DBSession.configure(
bind=engine,
extension=None
)
DBSession.configure(bind=engine)

# check alembic versions of the database
# and raise an error if it is not matching with the system
Expand Down Expand Up @@ -192,10 +189,11 @@ def get_alembic_version():
conn = DBSession.connection()
engine = conn.engine
if engine.dialect.has_table(conn, 'alembic_version'):
sql_query = 'select version_num from alembic_version'
sql_query = "select version_num from alembic_version"
from sqlalchemy import text
from sqlalchemy.exc import OperationalError, ProgrammingError
try:
return DBSession.connection().execute(sql_query).fetchone()[0]
return DBSession.connection().execute(text(sql_query)).fetchone()[0]
except (OperationalError, ProgrammingError, TypeError):
DBSession.rollback()
return None
Expand Down Expand Up @@ -257,10 +255,10 @@ def create_alembic_table():
Base.metadata.create_all(engine)

# first try to query the version value
sql_query = 'select version_num from alembic_version'
from sqlalchemy import text
sql_query = "select version_num from alembic_version"
try:
version_num = \
DBSession.connection().execute(sql_query).fetchone()[0]
version_num = DBSession.connection().execute(text(sql_query)).fetchone()[0]
except TypeError:
logger.debug('inserting %s to alembic_version table' % version_num)
# the table is there but there is no value so insert it
Expand All @@ -279,6 +277,7 @@ def __create_admin__():
"""creates the admin
"""
from stalker import defaults
from stalker.db.session import DBSession
from stalker.models.auth import User
from stalker.models.department import Department

Expand All @@ -291,7 +290,6 @@ def __create_admin__():

if not admin_department:
admin_department = Department(name=defaults.admin_department_name)
from stalker.db.session import DBSession
DBSession.add(admin_department)
# create the admins group

Expand Down Expand Up @@ -392,25 +390,30 @@ def create_ticket_statuses():
logger.debug("Ticket Types are created successfully")


def create_entity_statuses(entity_type='', status_names=None,
status_codes=None, user=None):
def create_entity_statuses(entity_type='', status_names=None, status_codes=None, user=None):
"""creates the default task statuses
"""
from stalker.db.session import DBSession
if not entity_type:
DBSession.rollback()
raise ValueError('Please supply entity_type')

if not status_names:
DBSession.rollback()
raise ValueError('Please supply status names')

if not status_codes:
DBSession.rollback()
raise ValueError('Please supply status codes')

# create statuses for entity
from stalker import Status, StatusList

logger.debug("Creating %s Statuses" % entity_type)

statuses = Status.query.filter(Status.name.in_(status_names)).all()
with DBSession.no_autoflush:
statuses = Status.query.filter(Status.name.in_(status_names)).all()

logger.debug('status_names: %s' % status_names)
logger.debug('statuses: %s' % statuses)
status_names_in_db = list(map(lambda x: x.name, statuses))
Expand All @@ -434,8 +437,8 @@ def create_entity_statuses(entity_type='', status_names=None,
)

# create the Status List
status_list = StatusList.query\
.filter(StatusList.target_entity_type == entity_type)\
status_list = StatusList.query \
.filter(StatusList.target_entity_type == entity_type) \
.first()

if status_list is None:
Expand All @@ -453,10 +456,10 @@ def create_entity_statuses(entity_type='', status_names=None,
status_list.statuses = statuses
DBSession.add(status_list)

from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError, OperationalError
try:
DBSession.commit()
except IntegrityError as e:
except (IntegrityError, OperationalError) as e:
logger.debug("error in DBSession.commit, rolling back: %s" % e)
DBSession.rollback()
else:
Expand Down
7 changes: 4 additions & 3 deletions stalker/db/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def save(self, data=None):
self.commit()


DBSession = ExtendedScopedSession(
sessionmaker(extension=None)
)
# try:
# DBSession = ExtendedScopedSession(sessionmaker(extension=None))
# except TypeError:
DBSession = ExtendedScopedSession(sessionmaker())
5 changes: 3 additions & 2 deletions stalker/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,9 @@ def __repr__(self):
def __eq__(self, other):
"""the equality operator
"""
return isinstance(other, SimpleEntity) and \
self.name == other.name
from stalker.db.session import DBSession
with DBSession.no_autoflush:
return isinstance(other, SimpleEntity) and self.name == other.name

def __ne__(self, other):
"""the inequality operator
Expand Down
12 changes: 9 additions & 3 deletions stalker/models/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,9 @@ def _end_getter(self):
will also set the end, so the timedelta between them is preserved,
default value is 10 days
"""
return self._end
from stalker.db.session import DBSession
with DBSession.no_autoflush:
return self._end

def _end_setter(self, end_in):
self._start, self._end, self._duration = \
Expand Down Expand Up @@ -489,7 +491,9 @@ def _start_getter(self):
class:`datetime.datetime` and the default value is
:func:`datetime.datetime.now(pytz.utc)`
"""
return self._start
from stalker.db.session import DBSession
with DBSession.no_autoflush:
return self._start

def _start_setter(self, start_in):
self._start, self._end, self._duration = \
Expand All @@ -510,7 +514,9 @@ def _duration(cls):
return Column('duration', Interval)

def _duration_getter(self):
return self._duration
from stalker.db.session import DBSession
with DBSession.no_autoflush:
return self._duration

def _duration_setter(self, duration_in):
if duration_in is not None:
Expand Down
5 changes: 4 additions & 1 deletion stalker/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class Project(Entity, ReferenceMixin, StatusMixin, DateRangeMixin, CodeMixin):
"inherit_condition": project_id == Entity.entity_id
}

# TODO: Remove this attribute, because we have the statuses to control if a project is active or not
active = Column(Boolean, default=True)

clients = association_proxy(
Expand Down Expand Up @@ -603,7 +604,9 @@ def _validate_user(self, key, user):
)

# also update rate attribute
self.rate = user.rate
from stalker.db.session import DBSession
with DBSession.no_autoflush:
self.rate = user.rate

return user

Expand Down
13 changes: 6 additions & 7 deletions stalker/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

from sqlalchemy import (Table, Column, Integer, ForeignKey, Boolean, Enum,
Float, event, CheckConstraint)
from sqlalchemy.exc import UnboundExecutionError, OperationalError, \
InvalidRequestError
from sqlalchemy.exc import UnboundExecutionError, OperationalError, InvalidRequestError, ProgrammingError
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relationship, validates, synonym, reconstructor

Expand Down Expand Up @@ -1878,7 +1877,7 @@ def _total_logged_seconds_getter(self):
engine = DBSession.connection().engine
result = engine.execute(text(sql), task_id=self.id).fetchone()
return result[0] if result[0] else 0
except (UnboundExecutionError, OperationalError) as e:
except (UnboundExecutionError, OperationalError, ProgrammingError) as e:
# no database connection
# fallback to Python
logger.debug('No session found! Falling back to Python')
Expand Down Expand Up @@ -3191,7 +3190,7 @@ def add_exclude_constraint(table, connection, **kwargs):
"""adds the PostgreSQL specific ExcludeConstraint
"""
from sqlalchemy import DDL
from sqlalchemy.exc import ProgrammingError
from sqlalchemy.exc import ProgrammingError, InternalError

if connection.engine.dialect.name == 'postgresql':
logger.debug('add_exclude_constraint is Running!')
Expand All @@ -3201,7 +3200,7 @@ def add_exclude_constraint(table, connection, **kwargs):
logger.debug('running "btree_gist" extension creation!')
create_extension.execute(bind=connection)
logger.debug('successfully created "btree_gist" extension!')
except ProgrammingError as e:
except (ProgrammingError, InternalError) as e:
logger.debug('add_exclude_constraint: %s' % e)

# create the ts_to_box sql function
Expand All @@ -3225,7 +3224,7 @@ def add_exclude_constraint(table, connection, **kwargs):
logger.debug(
'successfully created ts_to_box function'
)
except ProgrammingError as e:
except (ProgrammingError, InternalError) as e:
logger.debug(
'failed creating ts_to_box function!: %s' % e
)
Expand All @@ -3246,7 +3245,7 @@ def add_exclude_constraint(table, connection, **kwargs):
logger.debug(
'successfully created ExcludeConstraint for "TimeLogs" table!'
)
except ProgrammingError as e:
except (ProgrammingError, InternalError) as e:
logger.debug(
'failed creating ExcludeConstraint for TimeLogs table!: %s' % e
)
Expand Down
Loading

0 comments on commit 3e9a8db

Please sign in to comment.