Skip to content

Commit

Permalink
Merge pull request #27 from ambitioninc/develop
Browse files Browse the repository at this point in the history
Django4 with CastOnAssignFieldMixin (#25)
  • Loading branch information
geophphrie authored Jul 18, 2023
2 parents 6c97aaa + 4452e04 commit 8eaf85e
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 49 deletions.
76 changes: 76 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: django-kmatch tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master,develop ]

jobs:
tests:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
python: [ '3.7','3.8','3.9' ]
# Time to switch to pytest or nose2?
# nosetests is broken on 3.10
# AttributeError: module 'collections' has no attribute 'Callable'
# https://github.com/nose-devs/nose/issues/1099
django:
- 'Django~=3.2.0'
- 'Django~=4.0.0'
- 'Django~=4.1.0'
- 'Django~=4.2.0'
exclude:
- python: '3.7'
django: 'Django~=4.0.0'
- python: '3.7'
django: 'Django~=4.1.0'
- python: '3.7'
django: 'Django~=4.2.0'
services:
postgres:
image: postgres:14.5
env:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Install requirements
run: |
python --version
pip install --upgrade pip wheel setuptools
pip install -r requirements/requirements.txt
pip install -r requirements/requirements-testing.txt
pip install "${{ matrix.django }}"
pip freeze
- name: Run tests
env:
DB_SETTINGS: >-
{
"ENGINE":"django.db.backends.postgresql",
"NAME":"django_kmatch",
"USER":"postgres",
"PASSWORD":"postgres",
"HOST":"localhost",
"PORT":"5432"
}
run: |
python manage.py check
coverage run manage.py test django_kmatch
coverage report --fail-under=90
- name: Check style
run: flake8 django_kmatch
3 changes: 2 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include README.rst
include LICENSE
include LICENSE
recursive-include requirements *
2 changes: 0 additions & 2 deletions django_kmatch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# flake8: noqa
from .version import __version__
from .fields import KField

default_app_config = 'django_kmatch.apps.DjangoKmatchConfig'
53 changes: 37 additions & 16 deletions django_kmatch/fields.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
from jsonfield import JSONField
import json

from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import JSONField as DjangoJSONField
from ambition_utils.fields import CastOnAssignFieldMixin
from kmatch import K


class KField(JSONField):
class KField(CastOnAssignFieldMixin, DjangoJSONField):
"""Stores a kmatch pattern and returns a compiled K object.
The KField field stores a kmatch pattern in a JSONField. The pattern is compiled and returned as
a K object when accessing the field. Invalid kmatch patterns cannot be stored.
"""
The KField field stores a kmatch pattern in a JSONField. The pattern is compiled and returned as
a K object when accessing the field. Invalid kmatch patterns cannot be stored.
"""
description = 'A kmatch pattern'

def pre_init(self, value, obj):
"""
Used to obtain a K object for a provided pattern. Normally this is done in the to_python method
of a Django custom field. However, this field inherits JSONField, and JSONField had to do
conversions in the pre_init method.
"""
value = super(KField, self).pre_init(value, obj)
return K(value) if not isinstance(value, K) and value is not None else value
def __init__(self, *args, **kwargs):
self.dump_kwargs = kwargs.pop('dump_kwargs', {
'cls': DjangoJSONEncoder,
'separators': (',', ':')
})
self.load_kwargs = kwargs.pop('load_kwargs', {})

super().__init__(*args, **kwargs)

def get_db_prep_value(self, value, connection, prepared=False):
"""
Converts a K object to a pattern. This pattern will be serialized to JSON and saved as a
TextField.
Converts a K object to a pattern.
"""
if isinstance(value, K):
value = value.pattern
return super(KField, self).get_db_prep_value(value, connection, prepared=False)

if not self.null and value is None:
return json.dumps(value)

return super().get_db_prep_value(value, connection, prepared=False)

def to_python(self, value):
"""
Used to obtain a K object for a provided pattern.
"""
if isinstance(value, K) or value is None:
return value

if isinstance(value, str):
# We really should not ever get here - the only way would be if the json
# was invalid, in which case we'll end up with a value error anyway.
return K(json.loads(value))
else:
return K(value)
2 changes: 0 additions & 2 deletions django_kmatch/tests/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django_kmatch.fields

Expand Down
2 changes: 1 addition & 1 deletion django_kmatch/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.2.0'
__version__ = '2.0.3'
6 changes: 6 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Release Notes
=============

v2.0.3
------
* Python 3.8,3.9
* Django 3.2-4.2 only
* No longer depends on legacy jsonfield package

v1.2.0
------
* Python 3.7
Expand Down
1 change: 0 additions & 1 deletion requirements/requirements-testing.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
coverage
django-nose
flake8
mock
psycopg2
3 changes: 3 additions & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Django>=3.2
ambition-utils>=3.1.6
kmatch>=0.3.0
1 change: 0 additions & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""
import sys
from optparse import OptionParser

from settings import configure_settings

# Configure the default settings
Expand Down
51 changes: 38 additions & 13 deletions settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import json

from django.conf import settings

Expand All @@ -12,36 +13,60 @@ def configure_settings():
test_db = os.environ.get('DB', None)
if test_db is None:
db_config = {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'ambition',
'USER': 'ambition',
'PASSWORD': 'ambition',
'HOST': 'db'
}
elif test_db == 'postgres':
db_config = {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres',
'NAME': 'django_kmatch',
}
else:
raise RuntimeError('Unsupported test DB {0}'.format(test_db))

# Check env for db override (used for github actions)
if os.environ.get('DB_SETTINGS'):
db_config = json.loads(os.environ.get('DB_SETTINGS'))

installed_apps = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.admin',
'django_kmatch',
'django_kmatch.tests',
]

settings.configure(
TEST_RUNNER='django_nose.NoseTestSuiteRunner',
NOSE_ARGS=['--nocapture', '--nologcapture', '--verbosity=1'],
MIDDLEWARE_CLASSES=(),
SECRET_KEY='*',
DATABASES={
'default': db_config,
},
INSTALLED_APPS=(
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'django_kmatch',
'django_kmatch.tests',
),
ROOT_URLCONF='django_kmatch.urls',
INSTALLED_APPS=installed_apps,
DEBUG=False,
DEFAULT_AUTO_FIELD='django.db.models.AutoField',
TEST_RUNNER='django_nose.NoseTestSuiteRunner',
NOSE_ARGS=['--nocapture', '--nologcapture', '--verbosity=1'],
MIDDLEWARE=(
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware'
),
TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
]
}
}],
)
23 changes: 11 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ def get_version():
raise RuntimeError('Unable to find version string in {0}.'.format(VERSION_FILE))


def get_lines(file_path):
return open(file_path, 'r').read().split('\n')


install_requires = get_lines('requirements/requirements.txt')
tests_require = get_lines('requirements/requirements-testing.txt')

setup(
name='django-kmatch',
version=get_version(),
Expand All @@ -29,25 +36,17 @@ def get_version():
packages=find_packages(),
classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Framework :: Django',
],
license='MIT',
install_requires=[
'jsonfield==0.9.23',
'Django>=2.0',
'kmatch>=0.3.0'
],
tests_require=[
'psycopg2',
'django-nose>=1.4',
'mock>=1.0.1',
'coverage>=3.7.1',
],
install_requires=install_requires,
tests_require=tests_require,
test_suite='run_tests.run_tests',
include_package_data=True,
zip_safe=False,
Expand Down

0 comments on commit 8eaf85e

Please sign in to comment.