Skip to content

Commit e216511

Browse files
authored
Merge pull request #2 from dorosch/develop
Release 0.3.1
2 parents 25c83ff + 2b2209b commit e216511

File tree

11 files changed

+268
-28
lines changed

11 files changed

+268
-28
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
pip install ".[test]"
3636
3737
- name: Build coverage file
38+
working-directory: ./tests
3839
run: |
3940
pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=sqlalchemy_timescaledb
4041

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ docs/_build/
4343

4444
# PyCharm
4545
.idea/
46+
47+
# Alembic
48+
tests/migrations/versions/*

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sqlalchemy-timescaledb"
3-
version = "0.3"
3+
version = "0.3.1"
44
authors = [
55
{ name="Andrei Kliatsko", email="andrey.daraschenka@gmail.com" },
66
]
@@ -16,7 +16,8 @@ test = [
1616
"pytest-cov==4.0.0",
1717
"pytest-factoryboy==2.5.1",
1818
"sqlalchemy>=1.3",
19-
"psycopg2-binary==2.9.5"
19+
"psycopg2-binary==2.9.5",
20+
"alembic==1.9.4"
2021
]
2122

2223
[project.urls]

sqlalchemy_timescaledb/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from sqlalchemy.dialects import registry
2+
3+
4+
registry.register(
5+
'timescaledb',
6+
'sqlalchemy_timescaledb.dialect',
7+
'TimescaledbPsycopg2Dialect'
8+
)
9+
registry.register(
10+
'timescaledb.psycopg2',
11+
'sqlalchemy_timescaledb.dialect',
12+
'TimescaledbPsycopg2Dialect'
13+
)
14+
registry.register(
15+
'timescaledb.asyncpg',
16+
'sqlalchemy_timescaledb.dialect',
17+
'TimescaledbAsyncpgDialect'
18+
)

sqlalchemy_timescaledb/dialect.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
from sqlalchemy.dialects.postgresql.base import PGDDLCompiler
44
from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
55

6+
try:
7+
import alembic
8+
except ImportError:
9+
pass
10+
else:
11+
from alembic.ddl import postgresql
12+
13+
class TimescaledbImpl(postgresql.PostgresqlImpl):
14+
__dialect__ = 'timescaledb'
15+
616

717
class TimescaledbDDLCompiler(PGDDLCompiler):
818
def post_create_table(self, table):

tests/alembic.ini

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = ./migrations
6+
7+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
8+
# Uncomment the line below if you want the files to be prepended with date and time
9+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
10+
# for all available tokens
11+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
12+
13+
# sys.path path, will be prepended to sys.path if present.
14+
# defaults to the current working directory.
15+
prepend_sys_path = .
16+
17+
# timezone to use when rendering the date within the migration file
18+
# as well as the filename.
19+
# If specified, requires the python-dateutil library that can be
20+
# installed by adding `alembic[tz]` to the pip requirements
21+
# string value is passed to dateutil.tz.gettz()
22+
# leave blank for localtime
23+
# timezone =
24+
25+
# max length of characters to apply to the
26+
# "slug" field
27+
# truncate_slug_length = 40
28+
29+
# set to 'true' to run the environment during
30+
# the 'revision' command, regardless of autogenerate
31+
# revision_environment = false
32+
33+
# set to 'true' to allow .pyc and .pyo files without
34+
# a source .py file to be detected as revisions in the
35+
# versions/ directory
36+
# sourceless = false
37+
38+
# version location specification; This defaults
39+
# to migrations/versions. When using multiple version
40+
# directories, initial revisions must be specified with --version-path.
41+
# The path separator used here should be the separator specified by "version_path_separator" below.
42+
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
43+
44+
# version path separator; As mentioned above, this is the character used to split
45+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47+
# Valid values for version_path_separator are:
48+
#
49+
# version_path_separator = :
50+
# version_path_separator = ;
51+
# version_path_separator = space
52+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53+
54+
# the output encoding used when revision files
55+
# are written from script.py.mako
56+
# output_encoding = utf-8
57+
58+
sqlalchemy.url = driver://user:pass@localhost/dbname
59+
60+
61+
[post_write_hooks]
62+
# post_write_hooks defines scripts or Python functions that are run
63+
# on newly generated revision scripts. See the documentation for further
64+
# detail and examples
65+
66+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
67+
# hooks = black
68+
# black.type = console_scripts
69+
# black.entrypoint = black
70+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
71+
72+
# Logging configuration
73+
[loggers]
74+
keys = root,sqlalchemy,alembic
75+
76+
[handlers]
77+
keys = console
78+
79+
[formatters]
80+
keys = generic
81+
82+
[logger_root]
83+
level = WARN
84+
handlers = console
85+
qualname =
86+
87+
[logger_sqlalchemy]
88+
level = WARN
89+
handlers =
90+
qualname = sqlalchemy.engine
91+
92+
[logger_alembic]
93+
level = INFO
94+
handlers =
95+
qualname = alembic
96+
97+
[handler_console]
98+
class = StreamHandler
99+
args = (sys.stderr,)
100+
level = NOTSET
101+
formatter = generic
102+
103+
[formatter_generic]
104+
format = %(levelname)-5.5s [%(name)s] %(message)s
105+
datefmt = %H:%M:%S

tests/migrations/env.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from logging.config import fileConfig
2+
3+
from alembic import context
4+
from sqlalchemy import engine_from_config
5+
from sqlalchemy import pool
6+
7+
# this is the Alembic Config object, which provides
8+
# access to the values within the .ini file in use.
9+
config = context.config
10+
11+
# Interpret the config file for Python logging.
12+
# This line sets up loggers basically.
13+
if config.config_file_name is not None:
14+
fileConfig(config.config_file_name)
15+
16+
from models import Base #, database_url, Metric, User
17+
config.set_section_option(
18+
config.config_ini_section,
19+
"sqlalchemy.url",
20+
# database_url.render_as_string(hide_password=False)
21+
'timescaledb://user:password@0.0.0.0:5432/database'
22+
)
23+
24+
target_metadata = Base.metadata
25+
26+
27+
def run_migrations_offline() -> None:
28+
"""Run migrations in 'offline' mode.
29+
30+
This configures the context with just a URL
31+
and not an Engine, though an Engine is acceptable
32+
here as well. By skipping the Engine creation
33+
we don't even need a DBAPI to be available.
34+
35+
Calls to context.execute() here emit the given string to the
36+
script output.
37+
38+
"""
39+
url = config.get_main_option("sqlalchemy.url")
40+
context.configure(
41+
url=url,
42+
target_metadata=target_metadata,
43+
literal_binds=True,
44+
dialect_opts={"paramstyle": "named"},
45+
)
46+
47+
with context.begin_transaction():
48+
context.run_migrations()
49+
50+
51+
def run_migrations_online() -> None:
52+
"""Run migrations in 'online' mode.
53+
54+
In this scenario we need to create an Engine
55+
and associate a connection with the context.
56+
57+
"""
58+
connectable = engine_from_config(
59+
config.get_section(config.config_ini_section, {}),
60+
prefix="sqlalchemy.",
61+
poolclass=pool.NullPool,
62+
)
63+
64+
with connectable.connect() as connection:
65+
context.configure(
66+
connection=connection, target_metadata=target_metadata
67+
)
68+
69+
with context.begin_transaction():
70+
context.run_migrations()
71+
72+
73+
if context.is_offline_mode():
74+
run_migrations_offline()
75+
else:
76+
run_migrations_online()

tests/migrations/script.py.mako

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
${imports if imports else ""}
11+
12+
# revision identifiers, used by Alembic.
13+
revision = ${repr(up_revision)}
14+
down_revision = ${repr(down_revision)}
15+
branch_labels = ${repr(branch_labels)}
16+
depends_on = ${repr(depends_on)}
17+
18+
19+
def upgrade() -> None:
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade() -> None:
24+
${downgrades if downgrades else "pass"}

tests/migrations/versions/.empty

Whitespace-only changes.

tests/models.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,19 @@
33

44
from sqlalchemy import Column, String, DateTime, Float, Integer
55
from sqlalchemy import create_engine
6-
from sqlalchemy.dialects import registry
76
from sqlalchemy.engine import URL
87
from sqlalchemy.orm import declarative_base
98
from sqlalchemy.orm import sessionmaker, scoped_session
109

11-
registry.register(
12-
'timescaledb',
13-
'sqlalchemy_timescaledb.dialect',
14-
'TimescaledbPsycopg2Dialect'
15-
)
16-
registry.register(
17-
'timescaledb.psycopg2',
18-
'sqlalchemy_timescaledb.dialect',
19-
'TimescaledbPsycopg2Dialect'
20-
)
21-
registry.register(
22-
'timescaledb.asyncpg',
23-
'sqlalchemy_timescaledb.dialect',
24-
'TimescaledbAsyncpgDialect'
25-
)
26-
27-
engine = create_engine(
28-
URL.create(
29-
host=os.environ.get('POSTGRES_HOST', '0.0.0.0'),
30-
port=os.environ.get('POSTGRES_PORT', 5432),
31-
username=os.environ.get('POSTGRES_USER', 'user'),
32-
password=os.environ.get('POSTGRES_PASSWORD', 'password'),
33-
database=os.environ.get('POSTGRES_DB', 'database'),
34-
drivername=os.environ.get('DRIVERNAME', 'timescaledb')
35-
)
10+
database_url = URL.create(
11+
host=os.environ.get('POSTGRES_HOST', '0.0.0.0'),
12+
port=os.environ.get('POSTGRES_PORT', 5432),
13+
username=os.environ.get('POSTGRES_USER', 'user'),
14+
password=os.environ.get('POSTGRES_PASSWORD', 'password'),
15+
database=os.environ.get('POSTGRES_DB', 'database'),
16+
drivername=os.environ.get('DRIVERNAME', 'timescaledb')
3617
)
18+
engine = create_engine(database_url)
3719
Session = scoped_session(sessionmaker(bind=engine))
3820
Base = declarative_base()
3921

0 commit comments

Comments
 (0)