Skip to content

Commit bd53299

Browse files
committed
first alpha working ver
1 parent 8ecb567 commit bd53299

22 files changed

Lines changed: 912 additions & 100 deletions

.codeclimate.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: "2"
2+
checks:
3+
method-complexity:
4+
config:
5+
threshold: 8
6+
argument-count:
7+
config:
8+
threshold: 6
9+
method-count:
10+
config:
11+
threshold: 25
12+
method-length:
13+
config:
14+
threshold: 35
15+
file-lines:
16+
config:
17+
threshold: 500
18+
engines:
19+
bandit:
20+
enabled: true
21+
checks:
22+
assert_used:
23+
enabled: false

.codecov.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
coverage:
2+
precision: 2
3+
round: down
4+
range: "80...100"
5+
6+
status:
7+
project: yes
8+
patch: yes
9+
changes: yes
10+
11+
comment:
12+
layout: "reach, diff, flags, files"
13+
behavior: default
14+
require_changes: false # if true: only post the comment if coverage changes
15+
require_base: no # [yes :: must have a base report to post]
16+
require_head: yes # [yes :: must have a head report to post]
17+
branches: # branch names that can post comment
18+
- "master"

.flake8

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[flake8]
2+
ignore = ANN101, ANN102, W503, S101, A003, VNE003, I202
3+
max-complexity = 8
4+
max-line-length = 88
5+
import-order-style = pycharm
6+
exclude = p38venv,.pytest_cache
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: bug
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior:
15+
1. Go to '...'
16+
2. Click on '....'
17+
3. Scroll down to '....'
18+
4. See error
19+
20+
(Note: this should be a complete and concise piece of code that allows reproduction of an issue)
21+
22+
**Expected behavior**
23+
A clear and concise description of what you expected to happen.
24+
25+
**Screenshots**
26+
If applicable, add screenshots to help explain your problem.
27+
28+
**Versions (please complete the following information):**
29+
- Database backend used (mysql/sqlite/postgress)
30+
- Python version
31+
- `ormar` version
32+
- `pydantic` version
33+
- if applicable `fastapi` version
34+
35+
**Additional context**
36+
Add any other context about the problem here.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
contact_links:
2+
- name: I have a question ❓
3+
url: https://github.com/collerek/ormar/discussions
4+
about: If you have any question about the usage of ormar, please open a discussion first.
5+
- name: I want a new feature 🆕
6+
url: https://github.com/collerek/ormar/discussions
7+
about: If you would like to request or make a change/enhancement that is not trivial, please open a discussion first.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: enhancement
6+
assignees: ''
7+
8+
---
9+
10+
**Is your feature request related to a problem? Please describe.**
11+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12+
13+
**Describe the solution you'd like**
14+
A clear and concise description of what you want to happen.
15+
16+
**Describe alternatives you've considered**
17+
A clear and concise description of any alternative solutions or features you've considered.
18+
19+
**Additional context**
20+
Add any other context or screenshots about the feature request here.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This workflows will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: Upload Python Package
5+
6+
on:
7+
release:
8+
types: [created]
9+
10+
jobs:
11+
deploy:
12+
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v2
17+
- name: Set up Python
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: '3.x'
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install setuptools wheel twine
25+
- name: Build and publish
26+
env:
27+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29+
run: |
30+
python setup.py sdist bdist_wheel
31+
twine upload dist/*

.github/workflows/test-package.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: build
5+
6+
on:
7+
push:
8+
branches-ignore:
9+
- 'gh-pages'
10+
pull_request:
11+
branches: [ master ]
12+
13+
jobs:
14+
tests:
15+
name: "Python ${{ matrix.python-version }}"
16+
runs-on: ubuntu-latest
17+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'collerek/sqlalchemy-to-ormar'
18+
strategy:
19+
matrix:
20+
python-version: [3.6, 3.7, 3.8, 3.9]
21+
fail-fast: false
22+
23+
steps:
24+
- uses: actions/checkout@v2
25+
- name: Set up Python
26+
uses: actions/setup-python@v1
27+
with:
28+
python-version: ${{ matrix.python-version }}
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
33+
- name: Run sqlite
34+
env:
35+
DATABASE_URL: "sqlite:///testsuite"
36+
run: PYTHONPATH=. pytest --ignore venv --cov=sqlalchemy_to_ormar --cov=tests --cov-report=xml --cov-fail-under=100 --cov-report=term-missing
37+
- run: mypy --config-file mypy.ini sqlalchemy_to_ormar tests
38+
- name: Upload coverage
39+
uses: codecov/codecov-action@v1
40+
- name: Test & publish code coverage
41+
uses: paambaati/codeclimate-action@v2.7.5
42+
env:
43+
CC_TEST_REPORTER_ID: ${{ secrets.CC_COVERAGE_TOKEN }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ coverage.xml
5050
*.py,cover
5151
.hypothesis/
5252
.pytest_cache/
53+
.mypy_cache/
5354

5455
# Translations
5556
*.mo
@@ -84,6 +85,9 @@ ipython_config.py
8485
# pyenv
8586
.python-version
8687

88+
# test databses
89+
*.db
90+
8791
# pipenv
8892
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
8993
# However, in case of collaboration, if having platform-specific dependencies or dependencies

README.md

Lines changed: 132 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,139 @@
11
# sqlalchemy-to-ormar
22

3-
# **WORK IN PROGRESS**
4-
5-
Simple translator from `sqlalchemy` ORM models to `ormar` models.
3+
A simple auto-translator from `sqlalchemy` ORM models to `ormar` models.
64

75
The `ormar` package is an async mini ORM for Python, with support for **Postgres,
8-
MySQL**, and **SQLite**.
6+
MySQL**, and **SQLite**.
97

108
To learn more about ormar:
119

12-
* ormar [github][github]
13-
* ormar [documentation][documentation]
10+
* ormar [github][github]
11+
* ormar [documentation][documentation]
12+
13+
## Quickstart
14+
15+
```python
16+
from databases import Database
17+
from sqlalchemy import (
18+
Column,
19+
ForeignKey,
20+
Integer,
21+
MetaData,
22+
String,
23+
create_engine,
24+
DECIMAL,
25+
)
26+
from sqlalchemy.ext.declarative import declarative_base
27+
from sqlalchemy.orm import Session, relationship, sessionmaker
28+
29+
Base = declarative_base()
30+
Database_URL = "sqlite:///test.db"
31+
engine = create_engine(Database_URL, echo=True)
32+
33+
34+
# given sqlalchemy models you already have
35+
class User(Base):
36+
__tablename__ = "users"
37+
38+
id = Column(Integer, primary_key=True)
39+
name = Column(String)
40+
fullname = Column(String)
41+
nickname = Column(String)
42+
salary = Column(DECIMAL)
43+
44+
addresses = relationship(
45+
"Address", back_populates="user", cascade="all, delete, delete-orphan"
46+
)
47+
48+
49+
class Address(Base):
50+
__tablename__ = "addresses"
51+
id = Column(Integer, primary_key=True)
52+
email_address = Column(String, nullable=False)
53+
user_id = Column(Integer, ForeignKey("users.id"))
54+
55+
user = relationship("User", back_populates="addresses")
56+
57+
58+
# instantiate new Databases instance
59+
database = Database(Database_URL)
60+
# note that you need new metadata instance as table names in ormar
61+
# will remain the same and you cannot have two tables with same name in
62+
# one metadata, note that we bind it to the same engine!
63+
# (or you can create new one with same url)
64+
metadata = MetaData(engine)
65+
66+
# use sqlalchemy-to-ormar (not normally imports should be at the top)
67+
from sqlalchemy_to_ormar import ormar_model_str_repr, sqlalchemy_to_ormar
68+
69+
# convert sqlalchemy models to ormar
70+
OrmarAddress = sqlalchemy_to_ormar(Address, database=database, metadata=metadata)
71+
OrmarUser = sqlalchemy_to_ormar(User, database=database, metadata=metadata)
72+
73+
# you can print the ormar model
74+
# or save it to file and you have proper model definition created for you
75+
76+
address_str = ormar_model_str_repr(OrmarAddress)
77+
78+
# now you can print it or save to file
79+
print(address_str)
80+
# will print:
81+
82+
# class OrmarAddress(ormar.Model):
83+
#
84+
# class Meta(ormar.ModelMeta):
85+
# metadata=metadata
86+
# database=database
87+
# tablename="addresses"
88+
#
89+
# id = ormar.Integer(autoincrement=True, primary_key=True)
90+
# email_address = ormar.String(max_length=255, nullable=False)
91+
# user = ormar.ForeignKey(to=OrmarUser, related_name="addresses", name=user_id, nullable=True)
92+
93+
# if you want to skip column aliases if they match field names use skip_names_if_match flag
94+
user_model_str = ormar_model_str_repr(OrmarUser, skip_names_if_match=True)
95+
96+
# let's insert some sample data with sync sqlalchemy
97+
98+
Base.metadata.create_all(engine)
99+
LocalSession = sessionmaker(bind=engine)
100+
db: Session = LocalSession()
101+
102+
ed_user = User(name="ed", fullname="Ed Jones", nickname="edsnickname")
103+
address = Address(email_address="ed@example.com")
104+
address2 = Address(email_address="eddy@example.com")
105+
ed_user.addresses = [address, address2]
106+
107+
db.add(ed_user)
108+
db.commit()
109+
110+
# and now we can query it asynchronously with ormar
111+
async def test_ormar_queries():
112+
user = await OrmarUser.objects.select_related("addresses").get(name='ed')
113+
assert len(user.addresses) == 2
114+
assert user.nickname == 'edsnickname'
115+
assert user.fullname == 'Ed Jones'
116+
117+
addresses = await OrmarAddress.objects.select_related('user').all(user__name='ed')
118+
assert len(addresses) == 2
119+
assert addresses[0].user.nickname == 'edsnickname'
120+
assert addresses[1].user.nickname == 'edsnickname'
121+
122+
# run async
123+
import asyncio
124+
asyncio.run(test_ormar_queries())
125+
126+
# drop db
127+
Base.metadata.drop_all(engine)
128+
```
129+
130+
## Automap support
131+
132+
TBD -> translated from models auto-mapped by sqlalchemy
14133

15134
## Supported fields
16135

17-
`sqlalchemy-to-ormar` supports following sqlachemy field types:
136+
`sqlalchemy-to-ormar` supports following sqlalchemy field types:
18137

19138
* "integer": `ormar.Integer`,
20139
* "small_integer": `ormar.Integer`,
@@ -31,16 +150,19 @@ To learn more about ormar:
31150
## Supported relations
32151

33152
sqlalchemy-to-ormar supports both `ForeignKey` as well as `ManyToMany` relations
153+
although like `ormar` itself it will create relation field on one side of the relation only
154+
and other side will be auto-populated with reversed side.
34155

35156
## Known limitations
36157

37158
sqlalchemy to ormar right now does not support:
38159

39-
* composite (multi-column) primary keys and foreign keys (as ormar does not support
40-
them yet)
160+
* composite (multi-column) primary keys and foreign keys (as ormar does not support them
161+
yet)
162+
* `cascade` options from `relationship` are ignored, only the ones declared in sqlalchemy ForeignKey (ondelete, onupdate) are extracted
41163
* ManyToMany fields names customization (as ormar does not support them yet)
164+
* ManyToMany association table has to have primary key
42165
* Model inheritance
43166

44-
45167
[documentation]: https://collerek.github.io/ormar/
46168
[github]: https://github.com/collerek/ormar

0 commit comments

Comments
 (0)